Skip to content

fix(actions): seed a flow action's params with the row id (#3915 follow-up) - #3934

Merged
os-zhuang merged 1 commit into
mainfrom
claude/rest-actions-type-dispatch-y5vqfj
Jul 29, 2026
Merged

fix(actions): seed a flow action's params with the row id (#3915 follow-up)#3934
os-zhuang merged 1 commit into
mainfrom
claude/rest-actions-type-dispatch-y5vqfj

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Follow-up to #3919 / #3915. Found by dogfooding the merged fix against a running app, not by a test.

What the live run showed

#3919 gave POST /api/v1/actions/:object/:action its flow dispatch and documented it as "equivalent to POST /api/v1/automation/:target/trigger, without having to know the flow name." Booting examples/app-crm and invoking its real crm_convert_lead action showed the claim did not hold.

# the actions endpoint — reaches the engine, then dies at the first node
POST /api/v1/actions/crm_lead/crm_convert_lead  {"recordId":"<lead>"}
→ Flow 'crm_convert_lead_wizard' failed: Node 'get_lead' failed: get_record:
  refusing to run — 1 filter condition(s) resolved to nothing and were dropped
  from the query: `{recordId}` (at id).

# the trigger route — same flow, same record, runs fine
POST /api/v1/automation/crm_convert_lead_wizard/trigger  {"recordId":"<lead>","objectName":"crm_lead"}
→ {"success":true,"status":"paused","screen":{"nodeId":"screen_account", …}}

The params bag was built as { ...record, ...params }, so it carried the record's id but never recordId. ConvertLeadAction declares recordIdParam: 'recordId' and its flow reads {recordId} — a declared contract no dispatcher honoured, the declared ≠ enforced shape in miniature. The MCP run_action path had the identical gap; it just had never been pointed at a flow that reads {recordId}.

The fix

New seedFlowActionParams (shared by both headless surfaces via dispatchFlowAction) builds the bag in three layers, weakest first:

  1. the subject record's fields — unchanged, this is what seeds a flow's named isInput variables the way the record-change trigger does;
  2. the row id under the keys flow authors actually write — recordId and the <objectName>Id camelCase alias, the same two domains/automation.ts seeds for the trigger route — plus the action's own declared recordIdParam when it names a third key, sourced from recordIdField (default id, per spec: a declaration may want a non-id value such as token for revoke-session);
  3. the caller's explicit action params, which win outright.

recordId also falls back to the id named in the URL when the record never loaded (new-record / unreadable-record invocations).

Verification

Re-ran the same live invocation after rebuilding — the actions endpoint now returns the identical run the trigger route does:

{"success":true,"status":"paused","runId":"run_f61c12ff…",
 "screen":{"nodeId":"screen_account","objectName":"crm_account",
           "defaults":{"name":"Radium Labs"}, }}

defaults.name is carried over from the lead, so get_lead resolved {recordId} and ran.

Tests: 5 new regression cases (both seeded keys, a declared recordIdParam, a non-id recordIdField, explicit-param precedence, record-never-loaded). Full @objectstack/runtime suite green — 53 files / 756 tests.

Note on the test gap this exposed

#3919's unit tests mock automation.execute, so they pinned the call shape without noticing the bag lacked the key flows actually read. The new cases assert the params bag contents specifically, but the durable lesson is that a mocked automation service cannot validate a flow contract — the live run is what caught this.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DTTKgYriDB6RVrkYjxxnG1


Generated by Claude Code

…ow-up)

#3915 documented the REST actions endpoint's flow dispatch as "equivalent to
POST /api/v1/automation/:target/trigger, without having to know the flow
name". Invoking the CRM's real `crm_convert_lead` against a running server
showed the claim did not hold: the params bag carried the record's fields — so
`id` — but never `recordId`, so the flow's `get_lead` node died on
`{recordId}` resolving to nothing, while the same flow triggered directly
paused normally on its first screen.

`dispatchFlowAction` now seeds the row id under the same keys the trigger
route seeds (`recordId` + the `<objectName>Id` camelCase alias), plus the
action's declared `recordIdParam` (sourced from `recordIdField`, default `id`)
when it names a third key. Explicit action params still win over every seed.
The MCP `run_action` path shares the helper and the fix.

Only a live run could catch this: the unit tests mock `automation.execute`, so
they pinned the call shape without noticing the bag lacked the key flows read.
Regression tests now cover both seeded keys, a declared `recordIdParam`, a
non-id `recordIdField`, explicit-param precedence, and the record-never-loaded
case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTTKgYriDB6RVrkYjxxnG1
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 29, 2026 10:44am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime.

18 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via packages/runtime)
  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/runtime)
  • content/docs/permissions/authentication.mdx (via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime)
  • content/docs/plugins/packages.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/releases/implementation-status.mdx (via @objectstack/runtime)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang merged commit 385c4b0 into main Jul 29, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/rest-actions-type-dispatch-y5vqfj branch July 29, 2026 10:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants