You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Setup › Position › Add holder: the user picker lists names only, so two people with the same name can't be told apart #27410
Row 2 of #27356, from HotCRM's 18.2.0 browser pass (hotcrm#2320, Setup track, S2). Triage's per-row rule (6486657637) applies. Filed by seat domain:ui#2.
Observed
Position › Add holder opens a user picker that shows each user's name and nothing else. An admin can't tell two people with the same name apart, and can't confirm the account before granting the position.
ui/packages/app-shell/src/views/metadata-admin/PositionHoldersSection.tsx ≈L389 opens RecordPickerDialog on sys_user with no columns and no displayField.
RecordPickerDialog (ui/packages/fields/src/widgets/RecordPickerDialog.tsx ≈L358) defaults columns to the display field alone, so the dialog draws one column.
The holder list on the same section already shows the email under the name (≈L363–366), so the section's own rows and its picker disagree.
The dialog draws only the columns the user may read (readableColumns, ≈L643). An email column is dropped for a viewer who can't read it.
Suggested remedy
Pass columns naming the user's display field and email. Measure sys_user's field names and how the list rows build their label (≈L87). The picker's own read rule keeps an unreadable email out.
Tests:
the picker draws an email column;
CONTROL: selecting still adds the holder as before;
CONTROL: a viewer without read on email gets the name column only, if the existing test harness can express it; otherwise say so;
ablation.
Not a broken capability, so it stays p3 per triage.
Dispatch order · domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · 2026-10-29T05:32Z
You are the os-dev developer agent for exactly ONE card: #27410, 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/27410" -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-27410 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-27410 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=27410 --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 #27410, 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)
The picker's own read rule decides what it draws: RecordPickerDialog drops a column the viewer may not read (readableColumns). Do not add a second permission check, and do not fetch the email some other way.
ui/packages/app-shell/src/views/metadata-admin/PositionHoldersSection.tsx ≈L389 passes no columns and no displayField to RecordPickerDialog on sys_user, so the dialog draws only its default display column (RecordPickerDialog.tsx ≈L358 / ≈L596).
The section's holder rows build their label at ≈L87 (full_name || name || display_name || email || id) and show email underneath (≈L363–366). Measure which sys_user field holds the display name in the served schema. Do not hard-code a guess.
PM suggested route (optional; measurement wins)
Pass columns = the user's display field + email. If the dialog takes objectSchema for title resolution, pass what the section already has rather than fetching again.
Tests:
the picker draws an email column;
CONTROL: selection still adds the holder;
CONTROL: a viewer without read on email gets the name column only, if the harness can express it (otherwise say so in the report);
ablation: drop columns, and the email-column test goes red.
Claim: PM loop round 6
Session: session_01JQwxbXpF772qWJjojJvRXw
Account: os-warren
Branch: claude/project-thread-6kebat
Worktree: objectstack-issue-27410
Domain: domain:ui
Seat: domain:ui#2
File surface: ui/packages/app-shell/src/views/metadata-admin/PositionHoldersSection.tsx; tests beside it; root .changeset/ (stop on breach; explain in the report)
Container & model: S, mode:thread, model: TIER_DEFAULT, effort: low
Clause-②: no
Metadata-protocol: none
Responsibility: the Add holder user picker lists names only | RecordPickerDialog is opened with no columns, so it draws the display column alone | administrators granting a position in Setup
Thread-read: 6488864588
Premise: holds (PositionHoldersSection.tsx ≈L389 passes no columns; RecordPickerDialog.tsx ≈L358 defaults to the display column, on origin/main9f76037)
Serial constraints cleared: no open PR touches PositionHoldersSection.tsx.
Dispatch order: comment 6488864588. Card thread claude/project-thread-6kebat may start editing.
{
"issue": 27410,
"status": "done",
"branch": "claude/project-thread-6kebat",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27427",
"session": "session_01JQwxbZN5JqZpSuejtNPz6p",
"premise_still_valid": true,
"summary": "PositionHoldersSection opens RecordPickerDialog on sys_user with columns [name, email] (headers from the section's en/zh copy: Name/Email, 姓名/邮箱) and objectSchema = sys_user as served by useMetadata().objects (no new fetch). Mechanism assumptions measured true on origin/main ebed73bd: the picker received no columns/displayField and drew only its default display column; sys_user (platform-objects sys-user.object.ts) declares nameField name, fields name and email, and no full_name/display_name. Ruling kept: no second permission check, email read no other way; the picker's readableColumns decides what is drawn.",
"tests": "head d2e47a1a. Lock: pnpm --dir ui exec vitest run --maxWorkers=2 packages/app-shell/src/views/metadata-admin/PositionHoldersSection -> Test Files 4 passed (4), Tests 17 passed (17), VERDICT command-exit 0. New file PositionHoldersSection.pickerColumns-27410.test.tsx against the real RecordPickerDialog: picker draws name and email cells; CONTROL selection creates sys_user_position by name; CONTROL real PermissionProvider policy denying sys_user.email draws the name column only (the harness expresses it). Ablation via scripts/ablation-replace.mjs --delete on the columns prop (source-aliased, no dist leg): anchor 1->0, blob 7283c48e->2b0ce9a2, 1 failed | 2 passed (email-column test red, controls green); restore blob == HEAD 7283c48e, git diff HEAD empty.",
"gates": "head d2e47a1a after merging origin/main (first derivation refused as STALE TREE on ci.yml). dispatch-gates --commands: 34 families, each run, all exit 0; --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Extra: node scripts/check-sdui-manifest.mjs exit 0; pnpm --dir ui run check:new-line-citations exit 0 (0 new); pnpm --dir ui run type-check:scripts exit 0; pnpm --filter @object-ui/app-shell type-check VERDICT command-exit 0. A first citations run with the wrong path (repo root) exited 1 MODULE_NOT_FOUND, not a gate verdict; rerun as above. PR check-runs: in_progress at report time.",
"line_budget": "n/a: no ratcheted surface touched",
"files_changed": [
"ui/packages/app-shell/src/views/metadata-admin/PositionHoldersSection.tsx",
"ui/packages/app-shell/src/views/metadata-admin/PositionHoldersSection.pickerColumns-27410.test.tsx",
".changeset/27410-position-holder-picker-email.md"
],
"deviations": [
"changeset lists @object-ui/app-shell and @object-ui/console patch, mirroring the #27389 changeset (which lists app-shell + console); the order named console only",
"PR body folds .github/PULL_REQUEST_TEMPLATE.md headings (Why, Human Minimum) into the os-dev order",
"install used npm_config_nodedir=/opt/node22 pnpm install --frozen-lockfile (exit 0)"
],
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 — relay pr_create (PR 27427), relay post-stamped issue comment on 27410; plus git push (not a REST write)",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: code read only (not measured live): Setup permission set page, Assigned users Add opens RecordPickerDialog on sys_user with no columns · evidence: AssignedUsersSection.tsx passes objectName sys_user, title, baseFilter, no columns, so its picker draws the name column alone, the same gap as #27410 · dedupe words: AssignedUsersSection picker email, permission set add users names only, RecordPickerDialog sys_user columns"
]
}
Activity
objectstack-fleet commentedon Oct 29, 2026
domain:ui#2 · os-warren · session_01JQwxbXpF772qWJjojJvRXw
Dispatch order ·
domain:ui#2·session_01JQwxbXpF772qWJjojJvRXw· 2026-10-29T05:32ZYou are the os-dev developer agent for exactly ONE card: #27410, in your own card thread. Your role definition is
.claude/agents/os-dev.mdin 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-rootAGENTS.mdandui/AGENTS.mdbefore your first edit, and pm-dispatchreferences/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/27410" -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; itsBranch:line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWESTClaim: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. Worktreeobjectstack-issue-27410on that branch from a freshorigin/main(git checkout --detachthe main checkout first, thengit worktree add --no-track -B <branch> ../objectstack-issue-27410 origin/main);pnpm install(frozen lockfile); push withgit 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-runfirst); your report comment throughnode scripts/pm/post-stamped.mjs --comment=27410 --file=F --jsonrun 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 onerun_rerun_failedthrough the relay. Report both counts (api_writeswith endpoints,mcp_callswith tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, line 3Fixes #27410, then## 维护者速读six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon your diff, run each, record exits before any pipe, reconcile with--ran(mergeorigin/mainfirst if it refuses as STALE TREE);node scripts/check-sdui-manifest.mjsis 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 isprivate: true). ⛔ui/.github/**and the rootci.ymlare 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 reportpremise_still_valid: falsewith the evidence; ⛔ never force a fix.Ruling (cannot be re-ruled)
The picker's own read rule decides what it draws:
RecordPickerDialogdrops a column the viewer may not read (readableColumns). Do not add a second permission check, and do not fetch the email some other way.PM mechanism assumptions (measure; disproof welcome)
On
origin/main9f76037 (read-only):ui/packages/app-shell/src/views/metadata-admin/PositionHoldersSection.tsx≈L389 passes nocolumnsand nodisplayFieldtoRecordPickerDialogonsys_user, so the dialog draws only its default display column (RecordPickerDialog.tsx≈L358 / ≈L596).full_name || name || display_name || email || id) and showemailunderneath (≈L363–366). Measure whichsys_userfield holds the display name in the served schema. Do not hard-code a guess.PM suggested route (optional; measurement wins)
columns= the user's display field +email. If the dialog takesobjectSchemafor title resolution, pass what the section already has rather than fetching again.emailgets the name column only, if the harness can express it (otherwise say so in the report);columns, and the email-column test goes red.Scope
File surface:
ui/packages/app-shell/src/views/metadata-admin/PositionHoldersSection.tsx.changeset/Do not edit
ui/packages/fields/**.Tier
TIER_DEFAULT, effortlow(S).Clause-②: no.Metadata-protocol: none. Not governed. Changeset: app-shell is private, so follow the #27389 precedent (@object-ui/consolepatch).objectstack-fleet commentedon Oct 29, 2026
domain:ui#2 · os-warren · session_01JQwxbXpF772qWJjojJvRXw
Claim: PM loop round 6
Session:
session_01JQwxbXpF772qWJjojJvRXwAccount:
os-warrenBranch:
claude/project-thread-6kebatWorktree:
objectstack-issue-27410Domain:
domain:uiSeat:
domain:ui#2File surface:
ui/packages/app-shell/src/views/metadata-admin/PositionHoldersSection.tsx; tests beside it; root.changeset/(stop on breach; explain in the report)Container & model:
S,mode:thread,model: TIER_DEFAULT,effort: lowClause-②: no
Metadata-protocol: none
Responsibility:
the Add holder user picker lists names only|RecordPickerDialog is opened with no columns, so it draws the display column alone|administrators granting a position in SetupThread-read: 6488864588
Premise: holds (
PositionHoldersSection.tsx≈L389 passes nocolumns;RecordPickerDialog.tsx≈L358 defaults to the display column, onorigin/main9f76037)Serial constraints cleared: no open PR touches
PositionHoldersSection.tsx.Dispatch order: comment 6488864588. Card thread
claude/project-thread-6kebatmay start editing.objectstack-fleet commentedon Oct 29, 2026
domain:ui#2 · os-warren · session_01JQwxbZN5JqZpSuejtNPz6p
os-dev-report
{ "issue": 27410, "status": "done", "branch": "claude/project-thread-6kebat", "pr": "https://github.com/objectstack-ai/objectstack/pull/27427", "session": "session_01JQwxbZN5JqZpSuejtNPz6p", "premise_still_valid": true, "summary": "PositionHoldersSection opens RecordPickerDialog on sys_user with columns [name, email] (headers from the section's en/zh copy: Name/Email, 姓名/邮箱) and objectSchema = sys_user as served by useMetadata().objects (no new fetch). Mechanism assumptions measured true on origin/main ebed73bd: the picker received no columns/displayField and drew only its default display column; sys_user (platform-objects sys-user.object.ts) declares nameField name, fields name and email, and no full_name/display_name. Ruling kept: no second permission check, email read no other way; the picker's readableColumns decides what is drawn.", "tests": "head d2e47a1a. Lock: pnpm --dir ui exec vitest run --maxWorkers=2 packages/app-shell/src/views/metadata-admin/PositionHoldersSection -> Test Files 4 passed (4), Tests 17 passed (17), VERDICT command-exit 0. New file PositionHoldersSection.pickerColumns-27410.test.tsx against the real RecordPickerDialog: picker draws name and email cells; CONTROL selection creates sys_user_position by name; CONTROL real PermissionProvider policy denying sys_user.email draws the name column only (the harness expresses it). Ablation via scripts/ablation-replace.mjs --delete on the columns prop (source-aliased, no dist leg): anchor 1->0, blob 7283c48e->2b0ce9a2, 1 failed | 2 passed (email-column test red, controls green); restore blob == HEAD 7283c48e, git diff HEAD empty.", "gates": "head d2e47a1a after merging origin/main (first derivation refused as STALE TREE on ci.yml). dispatch-gates --commands: 34 families, each run, all exit 0; --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Extra: node scripts/check-sdui-manifest.mjs exit 0; pnpm --dir ui run check:new-line-citations exit 0 (0 new); pnpm --dir ui run type-check:scripts exit 0; pnpm --filter @object-ui/app-shell type-check VERDICT command-exit 0. A first citations run with the wrong path (repo root) exited 1 MODULE_NOT_FOUND, not a gate verdict; rerun as above. PR check-runs: in_progress at report time.", "line_budget": "n/a: no ratcheted surface touched", "files_changed": [ "ui/packages/app-shell/src/views/metadata-admin/PositionHoldersSection.tsx", "ui/packages/app-shell/src/views/metadata-admin/PositionHoldersSection.pickerColumns-27410.test.tsx", ".changeset/27410-position-holder-picker-email.md" ], "deviations": [ "changeset lists @object-ui/app-shell and @object-ui/console patch, mirroring the #27389 changeset (which lists app-shell + console); the order named console only", "PR body folds .github/PULL_REQUEST_TEMPLATE.md headings (Why, Human Minimum) into the os-dev order", "install used npm_config_nodedir=/opt/node22 pnpm install --frozen-lockfile (exit 0)" ], "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 — relay pr_create (PR 27427), relay post-stamped issue comment on 27410; plus git push (not a REST write)", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: code read only (not measured live): Setup permission set page, Assigned users Add opens RecordPickerDialog on sys_user with no columns · evidence: AssignedUsersSection.tsx passes objectName sys_user, title, baseFilter, no columns, so its picker draws the name column alone, the same gap as #27410 · dedupe words: AssignedUsersSection picker email, permission set add users names only, RecordPickerDialog sys_user columns" ] }