Skip to content

try_catch node: the Studio flow inspector's Error Variable help text names a TypeScript type (TryCatchErrorValue) #24576

Closed

Description

@objectstack-fleet

Filing gate: ① — user-visible product defect with a reproduction (platform help text shown to authors).

Path: ux-conventions.labels-over-machine-names
QA-source: #24570 · ux-conventions.labels-over-machine-names · acceptance[3]
Checklist priority: P1 (the item fails on this clause ⇒ inherit P1)
type: Bug — violates the item's declared convention that platform-built help text never names a code type, class or schema path
Reader who acts: the objectstack seat that owns packages/services/service-automation — a one-string edit in the node's configSchema.
Dup check (open + closed): objectstack search "TryCatchErrorValue help text errorVariable try catch node description" → 3 hits, none this defect: #22502 ($-named errorVariable acceptance), #15669 (type widening in the same file), #7546 (try-region step visibility).

reach: on a stock showcase boot, Studio → Automations → showcase_resilient_sync → select the "Push with retry" node → the Error Variable help reads "… a TryCatchErrorValue: nodeId, message, and iteration / item …" (framework 5b14b7f, console objectui 4016370).

Reproduction

  1. Boot the showcase, sign in as admin.
  2. Open /_console/studio/com.example.showcase/automations?surface=flow%3Ashowcase_resilient_sync.
  3. Click the "Push with retry" node (a try/catch node); read the inspector's Error Variable help text.
  4. Actual: "Variable holding the caught error in the catch region — a TryCatchErrorValue: nodeId, message, and iteration / item when the failure happened inside a loop body". Expected: plain-language help with no type name. 2 fresh loads.

Mechanism

The text is the node descriptor's own schema description: packages/services/service-automation/src/builtin/try-catch-node.ts, configSchema.properties.errorVariable.description (line 75 at 5b14b7f). The spec's describe for the same key (packages/spec/src/automation/control-flow.zod.ts) is already plain language ("$error (the default), or a name without a leading $ read as {{ name.message }} …") — the two have drifted; aligning the descriptor to the spec's wording fixes the surface.

Full evidence: #24570.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p2 · bug · domain:services · area:workflow · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-19T14:58Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Fresh read on origin/main 2667e2402c: packages/services/service-automation/src/builtin/try-catch-node.ts:75, errorVariable.description, names `TryCatchErrorValue`. The premise holds.
    • p2. The item fails, so its P1 is the upper bound (filing-gate.md:43). Within that bound the failure is a tax, not a wall (:48): authors see a code type in help text, and nothing stops them. So it takes one step down.
    • Lane domain:services: one string in the node's configSchema. Describe the fields in words (the failing node, the message, and the loop position when inside a loop), with no type name.

    Generated by Claude Code

  2. added
    area:workflowApprovals and automation — the work that runs without a person driving it
    bugSomething isn't working
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 19, 2026
  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 5 · 2026-10-19T20:48Z
    Session: session_01LR1cD9JqgkeqY7k9rrZH8s
    Account: os-warren (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24576-try-catch-help-words
    Worktree: objectstack-issue-24576
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    File surface: packages/services/service-automation/src/builtin/try-catch-node.ts (configSchema.properties.errorVariable.description only), a test beside it if one pins descriptor help text; a changeset; ⛔ not packages/spec/** (the spec's own describe is reported, not edited) (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: sonnet (the floor tier: one help string; dispatch-gates --tier printed no path-derived mandate for this surface)
    Clause-②: no
    Responsibility: the try/catch node descriptor's help text (this lane, service-automation) | platform-built help text names no code type (ux-conventions.labels-over-machine-names, #24570) | every author who opens a try/catch node in Studio (card)
    Thread-read: 6270289133
    Serial constraints cleared: no open PR touches service-automation/src/builtin/try-catch-node.ts (a file scan of every open PR at this claim). #24613 (seat 1, in flight) holds runtime-identity.ts, engine.ts and plugin.ts in the same package, different files.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24576,
    "status": "done",
    "branch": "claude/issue-24576-try-catch-help-words",
    "pr": "#24655",
    "session": "session_01LR1cD9JqgkeqY7k9rrZH8s — the dispatching seat's id (subagent run); the commit carries it as its Claude-Session trailer",
    "premise_still_valid": true,
    "summary": "The try_catch descriptor's errorVariable help (try-catch-node.ts, configSchema) no longer names TryCatchErrorValue. It now says, in words, that the variable defaults to $error and carries the id of the Try / Catch node (nodeId), the failure message, which normally names the failing step (message), the platform error code when the failing step reported one (code, which the old text omitted), and the zero-based pass and current item inside a loop (iteration, item). Measured on the real engine: nodeId is the Try / Catch node's own id (guard), not the failing inner step (push_step), which only the message names, so the wording follows that and not the "failing node" phrasing of the triage comment and A2. packages/spec is untouched; the spec describe reaches two author-facing surfaces other than Studio, reported below.",
    "tests": "Head f967f57 (final). Probe (throwaway vitest file on the real AutomationEngine, deleted before commit): bound $error printed for a plain failure {nodeId: guard, message: "Node 'push_step' failed: boom"}, a DUPLICATE_RECORD failure (+ code), and the same inside a loop (+ iteration 0, item {id: r1}). Served text read through engine.getActionDescriptor('try_catch'): PascalCase scan /\b[A-Z][a-z0-9]+(?:[A-Z][a-z0-9]*)+\b/ on the new text = null; control leg on the old text = ["TryCatchErrorValue"] (so the scan can fire). Suite: bash scripts/pm/os-verify-lock.sh -c 'NODE_OPTIONS=--max-old-space-size=4096 pnpm --filter @objectstack/service-automation exec vitest run --maxWorkers=2' → VERDICT command-exit 0, "Test Files 193 passed (193) / Tests 2580 passed (2580)". No new test and no ablation: nothing pins the try_catch descriptor help text (notify-node.test.ts and http-nodes.test.ts pin other nodes' descriptions), and the diff changes one string literal and adds a changeset.",
    "gates": "Locked: bash scripts/pm/os-verify-lock.sh -c 'NODE_OPTIONS=--max-old-space-size=4096 pnpm --workspace-concurrency=2 --filter "@objectstack/service-automation^..." build' → VERDICT command-exit 0; 'pnpm --filter @objectstack/service-automation typecheck' (tsc --noEmit && check:test-typecheck: OK, 0 files / 0 errors / 0 pinned) → VERDICT command-exit 0; the vitest run above → VERDICT command-exit 0. Derived list: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at f967f57 (2 paths, 12 changed lines) → 59 commands, each run with its exit captured before any pipe (redirect, then $?): 58 exit 0, 1 exit 3 (pnpm check:dual-build-cjs-loads: PREREQUISITE NOT MET, no dist/ for ~40 workspace packages, only the service-automation closure was built; NOT MEASURED, not red; CI's Build Core hosts it). node scripts/pm/dispatch-gates.mjs --ran ran.list → exit 0, "59 derived, 58 run, 1 NOT-MEASURED, 0 UNRUN". Recorded list: node scripts/check-adr-0087-registration.mjs --base origin/main → exit 0; node scripts/check-adr-0087-registration.mjs --self-test → exit 0; node scripts/check-changeset-no-major.mjs --base origin/main → exit 0; node scripts/check-changeset-no-major.mjs --self-test → exit 0; node scripts/check-ci-filter-parity.mjs → exit 0; node scripts/check-closing-keyword-parity.mjs → exit 0; node scripts/check-closing-keyword-parity.mjs --self-test → exit 0; node scripts/check-comment-mask-adoption.mjs → exit 0; node scripts/check-comment-mask-adoption.mjs --self-test → exit 0; node scripts/check-comment-mask-corpus.mjs → exit 0; node scripts/check-dts-emitted.mjs --self-test → exit 0; node scripts/check-empty-changeset.mjs --base origin/main → exit 0; node scripts/check-empty-changeset.mjs --self-test → exit 0; node scripts/check-issue-citations.mjs → exit 0; node scripts/check-keyed-text-bounds.mjs → exit 0; node scripts/check-keyed-text-bounds.mjs --self-test → exit 0; node scripts/check-platform-object-tenancy-census.mjs → exit 0; node scripts/check-platform-object-tenancy-census.mjs --self-test → exit 0; node scripts/check-plugin-teardown-shape.mjs → exit 0; node scripts/check-plugin-teardown-shape.mjs --self-test → exit 0; node scripts/check-position-name-fold-loaders.mjs → exit 0; node scripts/check-position-name-fold-loaders.mjs --self-test → exit 0; node scripts/check-registry-log-declared.mjs → exit 0; node scripts/check-registry-log-declared.mjs --self-test → exit 0; node scripts/check-rest-log-spy-declared.mjs → exit 0; node scripts/check-rest-log-spy-declared.mjs --self-test → exit 0; node scripts/check-system-context-census.mjs → exit 0; node scripts/check-system-context-census.mjs --self-test → exit 0; node scripts/check-tenant-audit-census.mjs → exit 0; node scripts/check-tenant-audit-census.mjs --self-test → exit 0; node scripts/check-undeclared-dep-imports.mjs → exit 0; node scripts/check-undeclared-dep-imports.mjs --self-test → exit 0; node scripts/docs-audit/check-affected-docs.mjs → exit 0; node scripts/docs-audit/check-drift-comment.mjs → exit 0; node scripts/pm/release-rehearsal-clone.mjs --self-test → exit 0; node scripts/release-pending-publish.mjs --self-test → exit 0; pnpm --filter @objectstack/spec run check:duration-unit-keys → exit 0; pnpm check:changeset-gate-self-tests → exit 0; pnpm check:cross-package-test-inputs → exit 0; pnpm check:doc-authoring → exit 0; pnpm check:driver-memory-census → exit 0; pnpm check:dts-closure → exit 0; pnpm check:dual-build-cjs-loads → exit 3; pnpm check:gitlink-declared → exit 0; pnpm check:issue-citations → exit 0; pnpm check:lean-entry-closure → exit 0; pnpm check:logger-receiver-detach → exit 0; pnpm check:nul-bytes → exit 0; pnpm check:objectui-changeset → exit 0; pnpm check:org-identifier → exit 0; pnpm check:page-declaration-shape → exit 0; pnpm check:pm-changeset-deadline-census → exit 0; pnpm check:published-files → exit 0; pnpm check:refd-timer-probe → exit 0; pnpm check:slot-lookup → exit 0; pnpm check:sourcemap-no-sources-content → exit 0; pnpm check:test-source-alias → exit 0; pnpm check:tier-file-adoption → exit 0; pnpm check:watch-hint-literal → exit 0. CI on PR #24655 at f967f57, read at report time: 2 completed (Packed-tarball smoke (opt-in): skipped; No other open PR may claim the same issue: success), 21 in_progress (Lint and Type Check lanes, Check Changeset, Check PR Size, Governed Surface Queue Guard, Check Documentation Links, filter, ...); no conclusion yet, in_progress is the honest value.",
    "mcp_calls": "0 — no MCP GitHub tool was called; reads were gh api REST reads (issue, comments, PR, check-runs)",
    "api_writes": "2 relay strokes via node scripts/pm/fleet-write/dispatch.mjs (POST /repos/objectstack-ai/objectstack/dispatches each; run executes as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/24655/assignees [os-warren], read-back "3527 byte(s) sent, 3527 stored — identical", assignees as the pull lists them: os-warren; (2) this os-dev-report comment → POST /repos//issues/24576/comments. Plus 2 git push of the branch (an empty-branch write probe, then the commit), which are not REST writes. No labels, no assignee on the card, no claim comment.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: b · reach: named producer · the spec describe for TryCatchConfig.errorVariable still says "The value is a TryCatchErrorValue: ..." and reaches two author-facing surfaces other than Studio, measured on origin/main 7c7741d plus the built tree: (1) the generated reference page content/docs/references/automation/control-flow.mdx, the errorVariable row of the TryCatchConfig table (line 247), built from the describes by packages/spec/scripts/build-docs.ts (the page's own "## TryCatchErrorValue" section, line 278, is the type's reference entry and names the type by design); (2) json-schema/automation/TryCatchConfig.json, errorVariable.description, the same sentence verbatim (read from the built file), shipped in the @objectstack/spec tarball (package.json files lists json-schema) and named by os generate's retirement message (packages/cli/src/commands/generate.ts, "node_modules/@objectstack/spec/json-schema/CATEGORY/TYPE.json") as the schema to point an editor at. NOT reached, measured: the Studio inspector (it reads the descriptor's configSchema; json-schema/automation/Flow.json holds neither errorVariable nor TryCatchErrorValue because FlowNode.config is a record; the sibling objectui checkout at its HEAD 4484a11, not the pinned .objectui-sha, has zero git-grep hits for the type or the sentence). Contract text: ux-conventions.labels-over-machine-names acceptance[3] "user-facing banners and help text carry no raw spec path, class name or tracker id"; its variants list names Studio surfaces only, so whether docs and editor hovers are inside it is the seat's call, and no editor hover was driven. The fix lives in packages/spec (the describe, then gen:schema and gen:docs), domain:spec; this PR does not touch it. Seam: spec:automation/TryCatchConfig.errorVariable describe → docs:content/docs/references/automation/control-flow.mdx errorVariable row | editor:json-schema/automation/TryCatchConfig.json. Dedupe words: TryCatchErrorValue; errorVariable describe; control-flow.mdx; json-schema TryCatchConfig; labels-over-machine-names",
    "carrier: none (noted, not filed — no wrong answer at a public door measured) · the spec describe of TryCatchErrorValue.nodeId reads "Node the failure is attributed to"; the engine binds the Try / Catch node's own id (guard), the failing inner step (push_step) is named only inside message. A reader taking "the failing node" literally is misled; the descriptor text in this PR says "the id of this Try / Catch node". Stated in the PR body so the next author of the spec describe finds it."
    ],
    "line_budget": "12 (additions 11 + deletions 1; 2 files: one string in try-catch-node.ts, one new changeset); dispatch-gates reads "changed lines: 12 (+11 / -1; 2 file(s)) ... under" the 3000 human-merge threshold",
    "deviations": [
    "NOT MEASURED locally: pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: it reads every workspace package's dist/, and the diff needs only the service-automation closure built). Building the whole farm is CI's; Build Core hosts the gate. The change is a string literal in a descriptor and cannot change a module's parse or load.",
    "NOT MEASURED: the inspector's help slot was not driven in a browser (no dogfood run); the served descriptor text was read at the source through getActionDescriptor. Sibling descriptors (notify, screen) already carry double-brace examples in help text.",
    "A2 corrected by measurement: the dispatch and the triage word nodeId as "the failing step/node"; the engine binds the Try / Catch node's own id. The text follows the measurement (see summary).",
    "The PM route "extend a test that pins descriptor text" was conditional and not met for try_catch (only other nodes' descriptions are pinned), so no test file is touched; file surface kept to the one string and the changeset.",
    "Two throwaway probe test files (zz-measure-24576, zz-probe-24576) were created in the worktree to measure and deleted before the commit; git status was clean at commit and at push.",
    "Commit trailers are the model-free pair AGENTS.md requires (Claude-Session + Co-authored-by: Claude); the harness attribution reminder yielded to it.",
    "Worktree removed after the PR opened (node_modules removed first, git worktree remove without --force)."
    ],
    "files_changed": [
    "packages/services/service-automation/src/builtin/try-catch-node.ts",
    ".changeset/24576-try-catch-help-words.md"
    ]
    }

  5. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24655 comment 6277659527 · domain:services seat 2 · 2026-10-19T21:30Z · head f967f573b. Landing waits for every check on the head.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 2 · os-warren · session_01LR1cD9JqgkeqY7k9rrZH8s · 2026-10-19T22:12Z

    Reading Value
    PR #24655 merged through the merge queue: auto_merge_enabled 2026-10-19T21:47Z → added_to_merge_queue 2026-10-19T21:48Z → merged 2026-10-19T22:12Z
    origin/main merge commit 31e0735b3b is an ancestor (git merge-base --is-ancestor, exit 0)
    This card closed completed by the PR's Fixes #24576; this act clears pm:dispatched and the assignee

    ACCEPT 6277659527 is on the PR. The spec's own copy of the sentence is #24659 (domain:spec).


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:workflowApprovals and automation — the work that runs without a person driving itbugSomething isn't workingdomain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions