Skip to content

Create / edit dialog: a unique-index refusal says "A record with this value already exists" without naming or highlighting the field #26891

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 2 Revenue (report comment 6444188882 on objectstack-ai/hotcrm#2320, finding D4; screenshots on branch claude/project-thread-urevq9 under qa/ and in the HotCRM project files qa-2320/2-revenue/); track 1 Sales (report comment 6443461344 on objectstack-ai/hotcrm#2320, finding S11 (account name); screenshots on branch claude/project-thread-2kd6ha under qa/ and in the HotCRM project files qa-2320/sales/). 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

  • New Product with SKU QA-2320-SKU when that SKU exists → "A record with this value already exists", no field highlighted (prd-12-create-dup-sku.png).
  • New Account named like an existing one → the same sentence; it does not say which field, nor link the existing record (acc-04-dup-name-result.png).

Who loses: the user has to guess which of the form's unique fields collided.

Expected

The refusal names the field by its label and marks it in the form (the field-level error the form already draws for required).

Dedupe

search_issues objectstack "unique constraint duplicate message A record with this value already exists does not name field": 8 hits. #7821 (closed) "REST: the UNIQUE_VIOLATION 409 message is hard-coded English and carries no field" is the same complaint at the REST layer; on 18.2.0 the console still shows the generic sentence with no field, so either the field now on the wire is not used by the dialog, or #7821's fix did not reach this path — the reader measures which. Others: #24951, #23701, #22105, #15631 (closed, other aspects).

Reader who acts: the console form dialog owner (domain:ui), with the REST error shape from #7821.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: confirmed p3 · domain:ui · area:records · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T21:05Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatch order · domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · 2026-10-28T01:49Z

    You are the os-dev developer agent for exactly ONE card: #26891, in your own card thread. Your role definition is .claude/agents/os-dev.md in the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-root AGENTS.md and ui/AGENTS.md before your first edit, and pm-dispatch references/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/26891" -H "Authorization: Bearer $GH_TOKEN" and /comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with [ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.

    Identity and branch: the seat's Claim: comment on this card is your identity; its Branch: line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWEST Claim: on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Take a third ui lane seat" (cmsg_01U5RdnAAmkSQmZbqh6FtqZ8Uc2nCvWvLyYyaVYaUhakpJ) one message with your branch name and wait. Read-only measuring is fine meanwhile. Worktree objectstack-issue-26891 on that branch from a fresh origin/main (git checkout --detach the main checkout first, then git worktree add --no-track -B <branch> ../objectstack-issue-26891 origin/main); pnpm install (frozen lockfile); push with git push -u origin <branch> (⛔ no force).

    Writes: PR only through the relay op pr_create (node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array [{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced; --dry-run first); your report comment through node scripts/pm/post-stamped.mjs --comment=26891 --file=F --json run ONCE with output captured to a file (no time-shaped stamp inside the fenced report JSON). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and one run_rerun_failed through the relay. Report both counts (api_writes with endpoints, mcp_calls with tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, line 3 Fixes #26891, then ## 维护者速读 six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on your diff, run each, record exits before any pipe, reconcile with --ran (merge origin/main first if it refuses as STALE TREE); node scripts/check-sdui-manifest.mjs is a gate on every ui card. Build the closure your tests import first (bash scripts/pm/os-verify-lock.sh -c "pnpm --workspace-concurrency=2 --filter '<pkg>^...' build"). Changeset in the root .changeset/ for each published package your diff changes (app-shell is private: true). ⛔ ui/.github/** and the root ci.yml are out of bounds. Hand back: when the PR and report are up, send the seat thread one message (PR, head sha, report gist); keep the thread open through review and drive CI to green. If the premise fails, stop and report premise_still_valid: false with the evidence; ⛔ never force a fix.

    Ruling (cannot be re-ruled)

    Triage 6453549276, verbatim: "Sibling: #26867 (dispatched, domain:server) makes the unique refusal name the field by its label. This card is the dialog half: mark the field. Land after #26867, or read its error shape." #26867 is closed (PR 5aad4e09, #26934), so read the shape it shipped. No maintainer ruling.

    PM mechanism assumptions (measure; disproof welcome)

    On origin/main 7eb613c3 (read-only check):

    • Server: packages/types/src/data-error-classification.ts ≈L1150–1170 returns 409 { error, code: 'UNIQUE_VIOLATION', developerMessage?, field?, object? }; the raw-driver branch ≈L1790–1802 sends field only when the column resolves (none for a composite index).
    • Client: packages/client/src/index.ts ≈L7600–7630 sets err.code, err.httpStatus = 409, err.details = errorBody (so details.field), and err.fields only for an array. ui/packages/data-objectstack/src/index.ts normaliseClientError passes it through.
    • The gap: ui/packages/react/src/utils/error-message.ts ≈L208–230 extractFieldErrors reads only array shapes (validationErrors, details.fields, fields) and returns null for this refusal, so ui/packages/components/src/renderers/form/form.tsx ≈L3002–3060 shows the banner and toast instead of form.setError.
    • Side effect to measure and name: ui/packages/plugin-detail/src/InlineEditSaveBar.tsx ≈L194 also calls extractFieldErrors, so inline edit starts marking the field too.

    PM suggested route (optional; measurement wins)

    In extractFieldErrors: when the code is UNIQUE_VIOLATION and a single string field is present (details.field or top-level field), return [{ field, message: <the server's sentence> }]; without field keep returning null (the banner stays). Tests: the helper with { code: 'UNIQUE_VIOLATION', httpStatus: 409, details: { error, code, field: 'sku' } } yields one entry; the same without field yields null; a form render where a rejected submit marks the sku input. A refusal test asserts code and status. Ablation: remove the new branch; both helper tests go red.

    Scope

    File surface: ui/packages/react/src/utils/error-message.ts and its test, ui/packages/components/src/renderers/form/form.tsx only if the render path needs it, tests beside them, root .changeset/ (@object-ui/react patch, @object-ui/components if touched). These files are in ui seat 1's area; seat 1 released this card to seat 2 (no in-flight work there). ⛔ No server change; ⛔ do not edit plugin-form/**, RecordFormPage.tsx, recordFormNavigation.ts or console/AppContent.tsx (#26843 in flight).

    Tier

    No path-derived mandate (dispatch-gates --tier, read 2026-10-28T01:49Z). TIER_DEFAULT, effort medium (S). Clause-②: no, Metadata-protocol: none expected.

  3. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2
    Session: session_01JQwxbXpF772qWJjojJvRXw
    Account: os-warren
    Branch: claude/project-thread-mgvheq
    Worktree: objectstack-issue-26891
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: ui/packages/react/src/utils/error-message.ts and its test, ui/packages/components/src/renderers/form/form.tsx only if the render path needs it, tests beside them, root .changeset/ (stop on breach; explain in the report)
    Container & model: S, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: extractFieldErrors in @object-ui/react reads only array field errors, so a UNIQUE_VIOLATION's single field is shown as a banner | the server names the field since #26867 (PR #26934) | every create/edit dialog user who hits a unique index
    Thread-read: 6458487083
    Premise: holds (server half merged; ui gap read on origin/main 7eb613c3; readings in dispatch order 6458487083)
    Serial constraints cleared: files are in ui seat 1's area; seat 1 released the card to seat 2 at 2026-10-28T01:48Z with nothing in flight on them. #26843 (seat 2) holds plugin-form/**, RecordFormPage.tsx, recordFormNavigation.ts, console/AppContent.tsx (not edited here).

    Dispatch order: comment 6458487083. Card thread claude/project-thread-mgvheq may start editing.

  4. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 26891,
      "status": "done",
      "branch": "claude/project-thread-mgvheq",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27043",
      "head": "a17ebf4e",
      "session": "session_01JQwxbZRo35mpCPrKXVXevY",
      "premise_still_valid": true,
      "summary": "extractFieldErrors (@object-ui/react) now turns a 409 UNIQUE_VIOLATION carrying a single string field (details.field on the raw client error, field on a hand-rolled one) into one WriteFieldError with the server's sentence; without field it still answers null and the banner stays. form.tsx is unchanged: its existing extractFieldErrors then form.setError path marks the input with no banner or toast. Premise measured on origin/main 7eb613c3: server arms emit field (envelope arm and raw-driver arm), client parks the flat body on err.details, normaliseClientError passes the 409 through, helper read arrays only. Side effect named: InlineEditSaveBar atomic path now attributes the refusal to the field.",
      "tests": "3 touched files: Test Files 3 passed, Tests 46 passed. Ablation via scripts/ablation-replace.mjs (unique branch -> return null, anchor 1->0, blob d3015007 -> 066939a9): 4 failed (the four new positive tests: two helper, one boundary, one form render) | 42 passed; restore blob == HEAD d3015007, git diff HEAD empty; tests resolve @object-ui/react to src via the ui vitest alias, no dist leg. Consumer sweep under the lock (plugin-detail __tests__, packages/react, console settings + system pages): Test Files 385 passed | 1 skipped, Tests 3607 passed | 18 skipped, VERDICT command-exit 0. type-check @object-ui/react + @object-ui/components: VERDICT command-exit 0. Closure build @object-ui/components^... VERDICT command-exit 0 (7m17s). All on head a17ebf4e.",
      "gates": "dispatch-gates --commands (tree a17ebf4e): 34 families, all exit 0, incl. node scripts/check-sdui-manifest.mjs, its --self-test, and pnpm --dir ui exec vitest run scripts/ (under the lock). --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. eslint on the four touched files: 0 errors. PR check-runs: in_progress at report time (not awaited, per os-dev).",
      "line_budget": "n/a: no ratcheted file touched; diff 4 code/test files + 1 changeset",
      "files_changed": [
        "ui/packages/react/src/utils/error-message.ts",
        "ui/packages/react/src/utils/error-message.test.ts",
        "ui/packages/react/src/utils/error-message.normalisation-boundary.test.ts",
        "ui/packages/components/src/renderers/form/__tests__/form-server-field-errors.test.tsx",
        ".changeset/26891-unique-refusal-marks-field.md"
      ],
      "deviations": [
        "os-dev rule 1 names branch claude/issue-N-slug; the card-thread branch claude/project-thread-mgvheq is the claim's Branch line (platform-assigned)",
        "PR body line 2 is the platform requested-by line between the attribution comment and Fixes, kept as the session context requires",
        "template headings Why and Human Minimum folded beside the os-dev section order",
        "origin/main moved to cb8988ee after the gates; no overlap with the touched paths, no merge (none required without a conflict)"
      ],
      "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0 GitHub MCP calls",
      "api_writes": "2: fleet-write relay pr_create (POST pulls, #27043, assignee os-warren) and this report comment via post-stamped (POST issues/26891/comments); plus git push of the branch (not a REST write)",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat review: PR #27043 ACCEPT (record on the PR, head a17ebf4eb3), 2026-10-28T02:56Z. Landing waits for every check on the head to be green.

  6. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Landing held · domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · 2026-10-28T03:15Z

    PR #27043 (ACCEPT 6459424656) is green on head a17ebf4eb3. Every check-run succeeded or was path-skipped, including the PR-body guards and Check Changeset that re-ran after the seat's body edit. mergeable_state is clean and the PR is not governed. This session's permission classifier refused the landing stroke (pr_ready + automerge_enable through the fleet-write relay, "Merge Without Review") on PR #27013 and on PR #27035 at 2026-10-28T02:42Z, so the seat does not retry it here. It asks the maintainer to mark the PR ready for review and enable auto-merge.

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uipriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions