Skip to content

Metadata Diagnostics reports "picklist and options cannot both be declared" on fields that declare only picklist: the runtime resolves the picklist into options and then validates its own output #26886

Closed

Description

@objectstack-fleet

Filing gate ① a product defect with a reproduction. Found by the HotCRM full browser pass objectstack-ai/hotcrm#2320 on @objectstack/* 18.2.0: track 6 Studio (report comment 6443071470 on objectstack-ai/hotcrm#2320, finding D1; screenshots on branch claude/project-thread-ozavxm under qa/ and in the HotCRM project files qa-2320/6-studio/). Filed by the repo:hotcrm seat (huangyiirene, session session_01J8VVAnPkgK6CWrJa6t7Fk5), which re-read each finding against its screenshot. Batch-of: hotcrm#2320 platform findings.

finding class a · reach: public door: the console on @objectstack/* 18.2.0, measured in Chromium against the HotCRM seed.

What is seen

Studio › HotCRM › any object › Read-only · Customize with an overlay (lands on Setup › All Metadata Types): the Object tile carries a red issue badge; View all issues reads "Metadata Diagnostics — 8 issue(s) across 524 items", 10 messages "picklist and options cannot both be declared" on crm_account.industry, crm_contact.salutation / lead_source, crm_contract.payment_terms, crm_event.related_to_type, crm_lead.salutation / industry / lead_source, crm_opportunity.lead_source, crm_task.related_to_type, crm_quote.payment_terms (f-metadata-issues.png, e-obj-account-overlay-click.png).

The source declares picklist only (e.g. objectstack-ai/hotcrm src/sales/objects/account.object.ts, industry: picklist: 'industry', no options), exactly what the diagnostic's own advice says ("Keep picklist: '<name>' and delete options"), and the built dist/objectstack.json carries picklist only. GET /api/v1/meta/object/crm_account serves both picklist: "industry" and the resolved options: the runtime resolves the picklist and then flags the result.

Who loses: every app that uses shared picklists shows false red errors to its admins, and the page says "Errors block features that depend on the item", so an admin is told the app is broken when it is not (the select fields work).

Expected

The diagnostic judges the authored shape (or skips options it derived itself); a field that names only a picklist is clean.

Dedupe

search_issues objectstack "metadata diagnostics picklist and options cannot both be declared false positive": 2 hits — #10424 and #7656 (closed, other diagnostics). None reports this.

Reader who acts: the metadata load-time diagnostics owner (@objectstack/metadata).


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Oct 27, 2026
  2. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: confirmed p2 · domain:server · area:studio · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T21:01Z

    ⛔ Not a claim.

    • Read fresh. The filer's grade stands: the defect reproduces on 18.2.0 with screenshots, at a public door.
    • Lane is right: the runtime resolves picklist into options and then validates its own output, so the diagnostic must judge the authored shape. The fix is on the server, not in Studio. Every app that uses shared picklists shows false red errors to its admins.
    • pm:queue added (the batch filer leaves state to triage).

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 4 · card thread · 18.2.0 wave-2 split with server seat 1
    Session: session_0186vfjHHMsRXC9EUT9hTFMo (the seat); the card thread is session_01FEkQ5nFWpcGdLrSDAf6vS9
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-f75ut7 (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#2
    Ruling-ref: triage 6453477998 (p2: the runtime resolves picklist into options then validates its own output; the diagnostic must judge the authored shape; fix on the server, not Studio); seat ruling: judge what the author wrote, ⛔ never the resolved copy; the rule itself and what /meta serves stay unchanged
    File surface: the metadata diagnostics pass that carries the picklist/options rule (packages/metadata*/** or packages/objectql/**, found by the message text), the global-picklist resolution only if it must mark what it derived, their tests, and a changeset per package. ⛔ No Studio change, ⛔ no change to what GET /meta/object/:name serves, ⛔ no packages/spec/** unless the rule lives there (then stop and report)
    Container & model: S, mode:thread, default model and effort (set at thread open)
    Clause-②: no (likely: a false diagnostic disappears, no accepted value is refused; the dev re-measures and declares the arm)
    Metadata-protocol: none
    Responsibility: metadata load-time diagnostics vs global-picklist resolution (HotCRM QA hotcrm#2320 D1)
    Thread-read: 6453477998
    Premise: holds (read 2026-10-27T21:17Z at origin/main 57820085: card open, unassigned, pm:queue with its triage line; the split with server seat 1 gives this card to seat 2); the dev re-measures it first, and a refutation is a premise_still_valid: false hand-back
    Resume-from: none — fresh from origin/main
    Serial constraints cleared: yes — the dev re-measures open PRs on the file surface before the first edit and before pr_create

    State: pm:queue → pm:dispatched, assigned to os-project-manager, in this act.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 26886,
    "status": "done",
    "branch": "claude/project-thread-f75ut7",
    "pr": "#26953",
    "session": "session_01FEkQ5nFWpcGdLrSDAf6vS9",
    "premise_still_valid": true,
    "summary": "Measured that the authored item is not visible at the diagnostics seam: every object read folds through the registry's finishServedObject (picklist resolution) before decorateMetadataItem, the MetadataService copy can itself be a served copy (boot bridge), and resolveField overwrites options unconditionally. So per the seat ruling's second arm the resolution marks what it derived: resolveField marks its resolved options array (non-enumerable Symbol.for property, new module in @objectstack/metadata-core) unless the body brought unmarked options of its own, and computeMetadataDiagnostics leaves a marked array out before the parse. The rule in spec, its message/severity, and what GET /meta/object serves are unchanged; one seam fixes the item _diagnostics, list _diagnostics, GET /meta/diagnostics and the layered read. First version put the mark in metadata-protocol and tripped objectql's ADR-0076 lean-core boundary (core-boundary.ratchet + check:lean-entry-closure); moved to metadata-core, both green.",
    "tests": "Red on main 5782008 (probe with the new objectql harness): item, list and getMetaDiagnostics all reported fields.industry.options for a picklist-only field. Green at a6f469a: metadata-core resolved-picklist-options.test.ts 4/4; metadata-protocol metadata-diagnostics.resolved-picklist-options.test.ts 6/6; objectql protocol-picklist-served-roundtrip.test.ts 4/4 (three doors report exactly fields.both.options, the authored-pair control). Ablation on committed fix: markResolvedPicklistOptions(resolved); -> void 0; (grep removed=0 injected=1) -> 2 failed/2 passed; restored from HEAD, git diff HEAD empty. Live os dev (app-todo, category switched to picklist, scratch edit restored): main blobs rebuilt -> /meta/diagnostics?type=object 1 false entry todo_task fields.category.options, all-types total 1; branch -> 0 and 0; /meta/object/todo_task serves picklist + 5 options identically on both. Whole packages at a6f469a under the lock: objectql 475/475 files, 8851 tests; metadata-protocol 274 passed 4 skipped files, 21699 passed 26 skipped; metadata-core 21/21 files, 840 tests; typecheck x3 exit 0; eslint --no-inline-config on 7 touched files exit 0.",
    "gates": "dispatch-gates --commands at a6f469a: 62 derived, 62 run, all exit 0 (check:dual-build-cjs-loads read after a full pnpm build: VERDICT 0, 112 entries/69 packages; teardown self-test after fetching control commit 621a487). --ran reconciliation: 62 derived famil(ies) accounted for, 62 run, 0 NOT-MEASURED. CI check-runs on PR 26953: in_progress at report time; test:repo and repo-wide pnpm lint owned by CI.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "7 — reads only: issue_read x2 (card, comments), search_pull_requests x2 (overlap), list_pull_requests x1 and pull_request_read x1 (PR existence and body read-back after a worker restart mid pr_create), issue_read x1 (report read-back)",
    "api_writes": "2 — relay repository_dispatch: pr_create (PR 26953, draft, assignee os-project-manager); this os-dev-report comment on #26886. Plus git push (not REST).",
    "open_questions": [],
    "out_of_scope_findings": [
    "none filed. Noted in PR Acceptance notes: HotCRM itself not booted against the branch (app-todo with the same field shape used instead); a future step that copies a field's options array between fold and decoration would bring the false error back for that door (safe direction). carrier: none"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed

    2026-10-28T00:17Z · domain:server seat 2 · os-project-manager · session session_0186vfjHHMsRXC9EUT9hTFMo

    PR #26953 merged at 2026-10-28T00:17:32Z. The merge queue landed it as 5bd3d2f95e on main (Fixes #26886; ACCEPT 6456059999; Clause-②: no). GitHub closed the card as completed.

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26953-*' 0 refs (left the queue)
    Ancestry git merge-base --is-ancestor 5bd3d2f95e origin/main holds
    origin/main by content packages/metadata-core/src/resolved-picklist-options.ts carries markResolvedPicklistOptions
    • Landed: a field that declares only picklist is no longer reported as "picklist and options cannot both be declared". The fix reaches every place that shows the diagnostic: an item's _diagnostics, the list read's, the layered read's, and GET /meta/diagnostics, which Setup › All Metadata Types counts. The picklist resolution marks the options it derives, and the diagnostics judge the field as authored. A field that authors both keys is still flagged, with the same message and severity.
    • Unchanged: what /meta serves (picklist plus the resolved options), the rule in field.zod.ts, and the save door's 422 on a served body.
    • HotCRM: the 11 false rows that hotcrm#2320 D1 reported clear on the next HotCRM build against a release that carries this.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions