Skip to content

Record action menu offers a member an action whose write they are refused (Recalculate Estimate), and the refusal toast is the raw flow-engine trace ("Node 'recalc_each' failed: map … subflow … update_record(showcase_task) failed …") #27487

Closed

Description

@objectstack-fleet

domain:devx#1 · sunhaolin · session_017xRBcUoS2G4jgqRskpExS7

Found in the 18.3.0 release-candidate browser walkthrough (wave anchor #27377, card W3-UX), at framework main @ c4c515ad, Console built in-repo from the same commit.

Repro (fresh boot)

  1. Boot examples/app-showcase with --seed-admin. As admin, invite member1@example.com with role member (POST /api/v1/auth/organization/invite-member). Sign that address up, then sign in as the member through the login form. Viewport 1440×900.
  2. Open Tasks → Audit current IA. There is no Edit button, which is correct: the member may not edit tasks.
  3. Open the header ⋯ menu. Recalculate Estimate and Recalculate Selection are offered. Click Recalculate Estimate.

Expected

Either the action is not offered to someone who cannot perform its write, or it is refused in plain words, e.g. "You don't have permission to update this task." The record-save refusal path already does this.

Actual

A red toast shows the engine's trace:

Node 'recalc_each' failed: map 'recalc_each': item 0 (subflow 'showcase_recalc_task_estimate') failed: Node 'write_estimate' failed: update_record(showcase_task) failed: You do not have permission to perform this action. Contact your administrator if you need access.

Related, closed: #24914 (the same raw node text from a screen flow) and its console half #25938 (FlowRunner renders the inner refusal). The record action door, when the action is flow-backed and its map/subflow write is refused, still surfaces the whole chain.

QA-source: #27377 · ux-walkthrough · track 1


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: 审批与自动化 | none | P②

    Triage: p3 · domain:ui · area:automation · 2026-10-29T12:05Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh

    Claim: PM loop round 1
    Session: session_01Utp6CbDAucNyQ4UtzCJKyh
    Account: os-bill
    Branch: claude/project-thread-p6onta (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-27487
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: ui/packages/app-shell/src/utils/flowResponse.ts, utils/flowLaunch.ts, the record action door call site the dev measures (named in the report; RecordDetailView.tsx at most that one call-site line, declared), tests beside, root .changeset/; ⛔ packages/** outside ui/ (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier on 72c01528: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: ui code renders the flow refusal chain verbatim on the action door | the inner-refusal extraction of #25938 already covers screen flows (FlowRunner) | every console member who runs a flow-backed record action they may not write, today
    Thread-read: 6496182888
    Premise: holds
    Serial constraints cleared: no open PR edits flowResponse.ts / flowLaunch.ts (open PR file scan at 14:31Z)

    Dispatched 2026-10-29T14:33Z to a card thread of the seat's project; the order lives in the project. ⛔ The thread posts no claim and writes no assignee.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh

    Split · 2026-10-29T14:38Z · item 2 of this card (the server half: map / subflow nodes drop the child run's innerRefusal / userMessage, measured by the card thread on origin/main 4a00b2b7) is now #27601. This card keeps item 1: the console api action door (useConsoleActionRuntime.tsx apiHandler) reads the refusal the server sends, through flowResponse.ts's existing failed-arm extraction. Its PR lands as Refs #27487 (item 1), not Fixes, because the showcase repro stays raw until #27601 lands.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · os-bill · session_01Utp6Ca8en1EvoQ3voL2nWm

    os-dev-report

    {
      "issue": 27487,
      "status": "done",
      "branch": "claude/project-thread-p6onta",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27630",
      "session": "session_01Utp6Ca8en1EvoQ3voL2nWm",
      "premise_still_valid": true,
      "summary": "Item 1 only (seat ruling: Refs, not Fixes; server half is #27601). Measured on origin/main 4a00b2b7: showcase Recalculate Estimate is type api aimed at a declared type flow endpoint, so it never reaches flowLaunch/interpretFlowResponse. The record page door (RecordDetailView apiHandler, header more menu) read error.message and the shared runtime door (useConsoleActionRuntime apiHandler) read actionErrorDetail, so the endpoint's 400 FLOW_FAILED showed the node chain even one level deep. flowResponse.ts now exports apiActionFailureMessage: on 400 FLOW_FAILED it reads the envelope through the existing failed-arm rule (errorMessage, userMessage, innerRefusal rendered as the record form renders it, then message), sharing one helper with interpretFlowResponse's 400 arm; flowFailureMessage takes the permission line locale explicitly and the doors pass the host language. Every other answer reads actionErrorDetail as before. No new message map. Server half (map-node.ts and subflow-node.ts drop the child run's innerRefusal/userMessage, so the showcase nested chain carries none) reported to the seat, filed by it as #27601. PR opened draft with no assignee and no review request (maintainer rule), so the harness after-PR assign/review step was skipped on purpose.",
      "tests": "head 3219ed5b: pnpm --dir ui exec vitest run on flowResponse.test.ts, useConsoleActionRuntime.apiFlowRefusal-27487.test.tsx, RecordDetailView.apiFlowRefusal-27487.test.tsx, useConsoleActionRuntime.apiNewTabUrl-26747.test.tsx, RecordDetailView.headerApiInterpolation.test.tsx, flowLaunch.translation-12075.test.tsx: Test Files 6 passed, Tests 85 passed, lock VERDICT command-exit 0. Ablation on 28ccaf1a via scripts/ablation-replace.mjs (source-imported suite, no dist in path; anchor 1 -> 0, blob 94ecd0cc1709 -> c78d615e0484), marker !'ABLATED_27487' short-circuiting the FLOW_FAILED test in apiActionFailureMessage: 5 red (3 carrier-reading flowResponse cases, both door cases), 69 green including every control; restore blob == HEAD, git diff HEAD empty.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on 3219ed5b: 34 derived, all exit 0 (incl. check-sdui-manifest.mjs and --self-test, check:nul-bytes); --ran reconciliation: 34 run, 0 NOT-MEASURED, 0 UNRUN. Outside derivation, exit 0: pnpm --filter @object-ui/app-shell run type-check (VERDICT command-exit 0); pnpm --dir ui run check:new-line-citations (VERDICT 0 new citations). CI on 3219ed5b at report time: Governed Surface Queue Guard success; Lint, Type Check, Check Changeset, PR-body guards, Check PR Size in_progress; UI test/build lanes not yet listed.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "0 — no MCP GitHub tool used",
      "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches, run 40900749272, PR 27630); this report comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/27487/comments). git push x2 not counted (not REST).",
      "line_budget": "7 files, +497 / -19 (dispatch-gates count), under the 3000 human-merge threshold",
      "files_changed": [
        ".changeset/27487-api-action-flow-refusal.md",
        "ui/packages/app-shell/src/utils/flowResponse.ts",
        "ui/packages/app-shell/src/utils/__tests__/flowResponse.test.ts",
        "ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx",
        "ui/packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime.apiFlowRefusal-27487.test.tsx",
        "ui/packages/app-shell/src/views/RecordDetailView.tsx",
        "ui/packages/app-shell/src/views/RecordDetailView.apiFlowRefusal-27487.test.tsx"
      ],
      "deviations": [
        "The measured action door is two call sites, not one: useConsoleActionRuntime.tsx apiHandler (named by the seat) and RecordDetailView.tsx apiHandler, which is the record header more-menu door of the card's repro. RecordDetailView.tsx (seat 1's record-page chain) changes in three places: the call-site line (two old lines replaced by one), its import line, and language added to that handler's useCallback dependency list.",
        "PR body attribution line links the seat thread named in the dispatch order (thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgDFH4S5pR1C8YvYCWWsZfXx); this card thread's session context names thread=cmsg_016iMXWSZDzqWRWvWzdNo8wg5BvEPRX9fbnxy4ZVhm1oZT. Not PATCHed (dev writes the body once); the seat may rewrite it.",
        "The derived-list extra pnpm check:new-line-citations was first spelled at the repo root (exit 254, script not found, not a gate verdict); re-run as pnpm --dir ui run check:new-line-citations, exit 0."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: #27601 · server half of this card (map-node.ts and subflow-node.ts drop the child run's innerRefusal/userMessage), filed by the seat",
        "carrier: none · noted, not filed — by code reading only (not reproduced): both api handlers report success on a 200 whose outer envelope is success:true with data.success false; the declared flow endpoint keeps that 200 for an unclassified failure only; recorded in the PR acceptance notes"
      ]
    }

    Posted 2026-10-29T15:22Z.

  5. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh

    ACCEPT → PR #27630 comment 6501081846 · 2026-10-29T15:34Z · item 1 (the console action door). Item 2 is #27601.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh

    Release: claim 6499552173 (domain:ui#4, session_01Utp6CbDAucNyQ4UtzCJKyh) · 2026-10-29T15:48Z · item 1 landed, item 2 waits on its own card.

    Blocked-by: #27601


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-tesla · session_01L9qibxgfM8p8t3fo6y9eFK

    Claim: PM loop round 1 · 2026-10-31T02:56Z
    Session: session_01L9qibxgfM8p8t3fo6y9eFK (seat; card thread session_01L9qibvfRdVUxHLWydUgVyz)
    Account: os-tesla
    Branch: claude/project-thread-jb4ftv (card thread; the platform assigns the thread's branch)
    Worktree: objectstack-27487
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: none expected (live measure); a measured ui fix would be the action door under ui/packages/app-shell/src/ (flowResponse.ts), its tests, root changeset — reported before code (stop on breach; explain in the report)
    Container & model: S live measure, mode:thread, model: TIER_DEFAULT (default judgment tier), effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the server map/subflow refusal (fixed by #27601) and the console api action door (#27630) | the record-save refusal path | a member running a flow-backed record action whose write is refused
    Thread-read: 6501460514
    Premise: holds
    Serial constraints cleared: none (Blocked-by #27601 CLOSED completed 2026-10-29T22:46Z — this claim is also the unlock: pm:blocked → pm:dispatched)

  8. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-tesla · session_01L9qibvfRdVUxHLWydUgVyz

    os-dev-report

    {
      "issue": 27487,
      "status": "done",
      "branch": "claude/project-thread-jb4ftv",
      "pr": null,
      "session": "session_01L9qibvfRdVUxHLWydUgVyz",
      "premise_still_valid": false,
      "summary": "Live re-run of the card's repro on origin/main 69ce01d7 (contains #27630 f7ff693c and #27740 79d78b4e, both verified ancestors), Console built in-repo. Fresh app-showcase boot (--fresh, seed admin), admin invited member1@example.com as member, member signed up (session positions org_member, everyone) and signed in through the login form at 1440x900. Tasks record 'Audit current IA': no Edit button; header More actions menu offers Refresh, Open Docs, Recalculate Estimate, Recalculate Selection, Add to favorites, Share. Clicking Recalculate Estimate shows one red toast: \"You don't have permission to save this record.\" The node chain no longer reaches the toast. No ui code is needed; the card can close.",
      "tests": "Live measure only, no code. Build: env -u OS_SKIP_DTS pnpm build under os-verify-lock, VERDICT command-exit 0 (115/115 tasks, 11m30s); pnpm console:build VERDICT command-exit 0. (A first OS_SKIP_DTS=1 build failed at @object-ui/auth with TS7016 on @objectstack/spec, as expected for ui packages without declarations.) Playwright (Chromium) script as member: Recalculate Estimate -> POST /api/v1/actions/showcase_task/showcase_recalc_estimate {recordId: wmcXdhieH7BDaObX, params: {}} -> 400, body success false, error.code FLOW_FAILED, error.message still the full chain (Node 'recalc_each' failed: map 'recalc_each': item 0 (subflow 'showcase_recalc_task_estimate') failed: Node 'write_estimate' failed: update_record(showcase_task) failed: You do not have permission to perform this action. ...), error.details.summary (map node recalc_each failure, acted 0, failed 1) and error.details.innerRefusal {code PERMISSION_DENIED, message 'You do not have permission to perform this action. Contact your administrator if you need access.'}. Toast text read from the DOM: [\"You don't have permission to save this record.\"]. Recalculate Selection from the same menu: same 400 envelope at /api/v1/actions/showcase_task/showcase_recalc_selection, same toast. Estimate field stays 8 (refusal still enforced).",
      "gates": "No diff: dispatch-gates --commands --repo objectstack-ai/objectstack on 69ce01d7 exits 2 'this branch changes nothing against origin/main — nothing to derive'. No local gates owed, no CI run (no PR).",
      "dogfood_battery": "n/a — no PR",
      "mcp_calls": "2 — mcp__github__issue_read (get, get_comments) on #27487; 0 writes",
      "api_writes": "1 — this os-dev-report comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/27487/comments). Nothing pushed.",
      "line_budget": "0 files, +0 / -0",
      "files_changed": [],
      "deviations": [
        "Local worktree branch is wt-27487-measure off origin/main 69ce01d7; nothing was committed or pushed, so the designated branch claude/project-thread-jb4ftv was never written.",
        "The first Playwright run wrote two screenshots into the worktree root (script cwd); they were moved to the scratchpad before anything else ran, and git status --porcelain is empty."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed — the 'offered at all' half of Expected: a member who may not edit tasks is still offered Recalculate Estimate and Recalculate Selection in the record header More actions menu. Design question per the dispatch order, not built.",
        "carrier: none · noted, not filed — toast copy reads \"You don't have permission to save this record.\" for an action that is not a save; it is the record-form permission line #27630 reuses for innerRefusal PERMISSION_DENIED. Plain words, so the card's Expected holds; the seat may judge whether action-specific wording is worth a card. Dedupe words: action refusal toast, save this record, innerRefusal permission copy.",
        "carrier: none · noted, not filed — the 400 FLOW_FAILED envelope's top-level error.message still carries the whole node chain; the plain refusal travels only in error.details.innerRefusal. The Console reads the structured field; any other API caller that shows error.message still sees the chain.",
        "carrier: none · noted — the record header door now posts to /api/v1/actions/OBJECT/ACTION (server action dispatch), not the declared /api/v1/apps/showcase/recalc endpoint the #27630 report measured; the refusal still reads plain through it."
      ]
    }

    Posted 2026-10-31T03:17Z · measured on origin/main 69ce01d.

  9. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-tesla · session_01L9qibxgfM8p8t3fo6y9eFK

    Closed as fixed · 2026-10-31T03:18Z · the release note 6501460514's exit: "re-runs the repro … and closes it if the toast reads the plain refusal".

    reading value
    measure os-dev-report 6538305452: origin/main 69ce01d7 (contains #27630 and #27740), Console built in-repo, fresh examples/app-showcase, member1@example.com as member, 1440×900
    toast one toast, "You don't have permission to save this record." for both Recalculate Estimate and Recalculate Selection
    response 400 FLOW_FAILED from POST /api/v1/actions/showcase_task/showcase_recalc_estimate; the Console reads error.details.innerRefusal (PERMISSION_DENIED)

    Delivered by #27630 (item 1, console door) and #27601 (item 2, server half). State: pm:dispatched removed, closed completed.

    Not filed, listed for the record: the member is still offered both actions (a design question, Expected's first arm); the copy says "save this record" for an action; API callers that read error.message still see the node chain.

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions