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
automation trigger door silently drops a non-object params (string or array) and dispatches the flow with an empty params bag #25821
Filing gate: ① a product defect with a live reproduction. Silent input coercion, finding class (a).
Business reason: a caller that sends params in the wrong shape gets the flow run anyway with no inputs. Either it fails later with a misleading "Unknown variable" or, worse, it succeeds doing the wrong thing. The resume door already refuses the same value-shape class (#9416), so the two doors of one engine disagree.
Reader: the packages/runtime automation domain (buildAutomationContext drops the value); the next PR touching trigger input handling.
Dedupe (open AND closed): "trigger params non-object silently dropped flow dispatch" → 6 hits, none of them this one.
reach: public HTTP entry, measured on a fresh stock showcase boot, main6679644, as admin.
Checklist item priority: P1
type: Bug
QA-source: #25817 · automation.trigger-status-contract · observed beside the clauses
Reproduction:
As admin, send POST /api/v1/automation/showcase_notify_owner/trigger with {"params":"oops"}. The flow dispatches with an empty params bag and fails with 400 FLOW_FAILED, "Unknown variable: ownerId".
{"params":[1,2]} behaves the same way.
Contrast: the resume door, given a non-object inputs, answers 400 VALIDATION_FAILED before anything runs.
Expected:400 VALIDATION_FAILED that names params as not an object, before dispatch, and no run row.
Claim: PM loop round 5 · card thread
Session: session_01R1q2xdtYgU6FKrZ7LgjPCC (the seat); the card thread is session_01Ny9nEFwaZEjuu6xpu6Y4gC
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-er748c (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: objectstack-issue-25821 (a linked worktree in the card thread's container)
Domain: domain:server
Seat: domain:server#1
Ruling-ref: triage 6399814705 (400 VALIDATION_FAILED naming params, before dispatch, no run row, the resume door's refusal class #9416)
File surface: packages/runtime/src/domains/automation.ts (the trigger door, buildAutomationContext) and any door that shares the same trigger dispatch, their tests, one patch changeset. ⛔ No packages/spec/** (stop on breach; explain in the report)
Container & model: S, mode:thread, default model and effort (set at thread open)
Clause-②: no
Metadata-protocol: none
Responsibility: the trigger door drops a non-object params and dispatches the flow with an empty bag (the card's live reproduction on main6679644a)
Thread-read: 6399814705
Premise: holds (read 2026-10-26T06:03Z: card open, unassigned, pm:queue, no other seat's claim; #25820's PR #25944, same file, is on main)
Serial constraints cleared: #25820 (same file) has landed; no open PR names #25821
{
"issue": 25821,
"status": "done",
"branch": "claude/project-thread-er748c",
"pr": "https://github.com/objectstack-ai/objectstack/pull/25974",
"head": "7f0c45b4",
"session": "session_01Ny9nEFwaZEjuu6xpu6Y4gC",
"premise_still_valid": true,
"summary": "buildAutomationContext (packages/runtime/src/domains/automation.ts) now refuses a params that is not a JSON object (string, number, boolean, array, null) with 400 VALIDATION_FAILED, one fields[] entry {field:'params', code:'invalid_type'} naming the received type, thrown before execute(), so no run row. The check is one function, objectValueFailure(field, value), hoisted out of the resume door's #9416 value-shape loop; the resume door now calls it for inputs/variables/output (answers byte-identical) and the trigger builder calls it for params. Undefined stays absent (flat-body branch unchanged); null is refused because the resume door refuses inputs:null. Doors now refusing: POST /automation/:name/trigger, legacy POST /automation/trigger/:name, and every declared type:'flow' endpoint (all three build their context in this one builder). Not sharing it: the action door (own params read), flow-to-flow nodes (in-process execute), MCP (no flow-start tool). Mechanism hypothesis check: the resume door's shape check was inline, not a named function, so it was hoisted and named rather than reused as-is. Departure from the claim: the claim carries 'Clause-②: no'; measured on this diff it is 'no (narrowing)' (request shapes that used to dispatch are refused), declared so in the PR body and changeset with a BREAKING banner and adr-0087 not-required (no-migration-prescription), matching precedent changesets 25572/25491. Contract review is owed for the narrowing arm.",
"tests": "New packages/runtime/src/domains/automation-trigger-params-shape.test.ts (42 cases: 9 non-object shapes x 2 REST spellings + flat-key null case x 2 + 9 shapes on a declared flow endpoint; 6 accepted bodies x 2 + 1 endpoint preservation case). Red: automation.ts restored from base 9ca36b16 -> 29 failed / 13 passed. Green: 42/42; with 7 neighbouring suites 8 files / 215 tests passed. Ablation via scripts/ablation-replace.mjs ('if (paramsFailure) {' -> 'if (paramsFailure && false) {', anchor 1->0, blob moved, restored to HEAD blob, git diff HEAD empty): 29 failed / 13 passed. Runtime tests import src, so no dist leg. pnpm --filter @objectstack/runtime typecheck && test at 7f0c45b4: lock VERDICT command-exit 0, Test Files 448 passed, Tests 7224 passed / 19 skipped. Live fresh showcase boot at 7f0c45b4: both trigger routes x {params:'oops'}, [1,2], null+ownerId, 42 -> 400 VALIDATION_FAILED naming params; control {params:{}} still dispatches (400 FLOW_FAILED Unknown variable ownerId); runs list afterwards = 2 (the controls only); declared endpoint POST /api/v1/apps/showcase/inquiries/purge {params:'oops'} -> 400 VALIDATION_FAILED.",
"gates": "dispatch-gates --commands at 7f0c45b4 (after merging origin/main 073f08b3): 69 families, all 69 exit 0 (check-plugin-teardown-shape --self-test, check:dts-closure, check:dual-build-cjs-loads first exited 3/1/3 before the full build and unshallow fetch, rerun exit 0). --ran reconciliation: 69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN. eslint --no-inline-config packages/runtime: exit 0. pnpm turbo run build: 121/121 tasks, lock VERDICT command-exit 0. PR check-runs: in_progress at report time (not waited on, per os-dev).",
"dogfood_battery": "7f0c45b4 · os-verify-lock VERDICT command-exit 0 · Test Files 296 passed / 3 skipped (299) · Tests 2523 passed / 18 skipped (2541) · no failures",
"mcp_calls": "0",
"api_writes": "2 relay strokes — pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST /issues/25974/assignees os-project-manager, run 40284892892, read-back identical) and this os-dev-report comment (POST /issues/25821/comments). Plus 3 git pushes (not REST). Reads via gh api REST only.",
"open_questions": [],
"deviations": [
"Clause-② declared 'no (narrowing)' where the claim wrote 'no' — measured value, as the dispatch order asked; the seat runs the contract review.",
"origin/main moved 10 commits after the pre-PR merge; none touch domains/automation.ts, endpoint-executor.ts or http-dispatcher.ts and git merge-tree is clean, so the head was not re-merged; CI runs on the merge ref."
],
"out_of_scope_findings": [
"class: a · reach: public door, fresh showcase boot at 7f0c45b4 (action door untouched by this diff): POST /api/v1/actions/showcase_task/showcase_bulk_reassign with {params:'oops'} and with {params:[1,2]} each answered 200 and started a paused run, the same as {params:{}} · evidence: domains/actions.ts reads reqParams = (params && typeof params === 'object') ? params : {}, the guard this PR removed from the trigger builder; objectValueFailure is the ready check · dedupe: action door params non-object silently dropped; actions params string array; flow action params shape refusal",
"class: a · reach: public door, same boot: POST /api/v1/automation/showcase_notify_owner/trigger with a JSON string body \"oops\" dispatched with an empty bag (400 FLOW_FAILED Unknown variable ownerId), and an array body [1,2] forwarded its indices as params · evidence: buildAutomationContext's ctxBody = body && typeof body === 'object' ? body : {} (an array passes); the resume door refuses a non-object body as (body) invalid_type; the #25821 ruling covers params only, so not fixed here · dedupe: trigger body non-object array dispatched; automation trigger body shape; trigger door body refusal"
]
}
State change: this card closed by Fixes (completed); pm:dispatched removed. Its two sibling doors (the action door's params, the trigger door's body) are #25976, dispatched on top of it (claim 6411222289).
Activity
objectstack-fleet commentedon Oct 25, 2026
Triage: first grade —
bug·priority:p2·domain:server·area:automation·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-25T15:55Z⛔ Not a claim, ⛔ not a dispatch.
400 VALIDATION_FAILEDnamingparams, before dispatch, with no run row.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 5 · card thread
Session:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat); the card thread issession_01Ny9nEFwaZEjuu6xpu6Y4gCAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-er748c(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree:
objectstack-issue-25821(a linked worktree in the card thread's container)Domain:
domain:serverSeat:
domain:server#1Ruling-ref: triage 6399814705 (
400 VALIDATION_FAILEDnamingparams, before dispatch, no run row, the resume door's refusal class #9416)File surface:
packages/runtime/src/domains/automation.ts(the trigger door,buildAutomationContext) and any door that shares the same trigger dispatch, their tests, onepatchchangeset. ⛔ Nopackages/spec/**(stop on breach; explain in the report)Container & model:
S,mode:thread, default model and effort (set at thread open)Clause-②: no
Metadata-protocol: none
Responsibility: the trigger door drops a non-object
paramsand dispatches the flow with an empty bag (the card's live reproduction onmain6679644a)Thread-read: 6399814705
Premise: holds (read 2026-10-26T06:03Z: card open, unassigned,
pm:queue, no other seat's claim; #25820's PR #25944, same file, is onmain)Serial constraints cleared: #25820 (same file) has landed; no open PR names #25821
objectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 25821, "status": "done", "branch": "claude/project-thread-er748c", "pr": "https://github.com/objectstack-ai/objectstack/pull/25974", "head": "7f0c45b4", "session": "session_01Ny9nEFwaZEjuu6xpu6Y4gC", "premise_still_valid": true, "summary": "buildAutomationContext (packages/runtime/src/domains/automation.ts) now refuses a params that is not a JSON object (string, number, boolean, array, null) with 400 VALIDATION_FAILED, one fields[] entry {field:'params', code:'invalid_type'} naming the received type, thrown before execute(), so no run row. The check is one function, objectValueFailure(field, value), hoisted out of the resume door's #9416 value-shape loop; the resume door now calls it for inputs/variables/output (answers byte-identical) and the trigger builder calls it for params. Undefined stays absent (flat-body branch unchanged); null is refused because the resume door refuses inputs:null. Doors now refusing: POST /automation/:name/trigger, legacy POST /automation/trigger/:name, and every declared type:'flow' endpoint (all three build their context in this one builder). Not sharing it: the action door (own params read), flow-to-flow nodes (in-process execute), MCP (no flow-start tool). Mechanism hypothesis check: the resume door's shape check was inline, not a named function, so it was hoisted and named rather than reused as-is. Departure from the claim: the claim carries 'Clause-②: no'; measured on this diff it is 'no (narrowing)' (request shapes that used to dispatch are refused), declared so in the PR body and changeset with a BREAKING banner and adr-0087 not-required (no-migration-prescription), matching precedent changesets 25572/25491. Contract review is owed for the narrowing arm.", "tests": "New packages/runtime/src/domains/automation-trigger-params-shape.test.ts (42 cases: 9 non-object shapes x 2 REST spellings + flat-key null case x 2 + 9 shapes on a declared flow endpoint; 6 accepted bodies x 2 + 1 endpoint preservation case). Red: automation.ts restored from base 9ca36b16 -> 29 failed / 13 passed. Green: 42/42; with 7 neighbouring suites 8 files / 215 tests passed. Ablation via scripts/ablation-replace.mjs ('if (paramsFailure) {' -> 'if (paramsFailure && false) {', anchor 1->0, blob moved, restored to HEAD blob, git diff HEAD empty): 29 failed / 13 passed. Runtime tests import src, so no dist leg. pnpm --filter @objectstack/runtime typecheck && test at 7f0c45b4: lock VERDICT command-exit 0, Test Files 448 passed, Tests 7224 passed / 19 skipped. Live fresh showcase boot at 7f0c45b4: both trigger routes x {params:'oops'}, [1,2], null+ownerId, 42 -> 400 VALIDATION_FAILED naming params; control {params:{}} still dispatches (400 FLOW_FAILED Unknown variable ownerId); runs list afterwards = 2 (the controls only); declared endpoint POST /api/v1/apps/showcase/inquiries/purge {params:'oops'} -> 400 VALIDATION_FAILED.", "gates": "dispatch-gates --commands at 7f0c45b4 (after merging origin/main 073f08b3): 69 families, all 69 exit 0 (check-plugin-teardown-shape --self-test, check:dts-closure, check:dual-build-cjs-loads first exited 3/1/3 before the full build and unshallow fetch, rerun exit 0). --ran reconciliation: 69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN. eslint --no-inline-config packages/runtime: exit 0. pnpm turbo run build: 121/121 tasks, lock VERDICT command-exit 0. PR check-runs: in_progress at report time (not waited on, per os-dev).", "dogfood_battery": "7f0c45b4 · os-verify-lock VERDICT command-exit 0 · Test Files 296 passed / 3 skipped (299) · Tests 2523 passed / 18 skipped (2541) · no failures", "mcp_calls": "0", "api_writes": "2 relay strokes — pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST /issues/25974/assignees os-project-manager, run 40284892892, read-back identical) and this os-dev-report comment (POST /issues/25821/comments). Plus 3 git pushes (not REST). Reads via gh api REST only.", "open_questions": [], "deviations": [ "Clause-② declared 'no (narrowing)' where the claim wrote 'no' — measured value, as the dispatch order asked; the seat runs the contract review.", "origin/main moved 10 commits after the pre-PR merge; none touch domains/automation.ts, endpoint-executor.ts or http-dispatcher.ts and git merge-tree is clean, so the head was not re-merged; CI runs on the merge ref." ], "out_of_scope_findings": [ "class: a · reach: public door, fresh showcase boot at 7f0c45b4 (action door untouched by this diff): POST /api/v1/actions/showcase_task/showcase_bulk_reassign with {params:'oops'} and with {params:[1,2]} each answered 200 and started a paused run, the same as {params:{}} · evidence: domains/actions.ts reads reqParams = (params && typeof params === 'object') ? params : {}, the guard this PR removed from the trigger builder; objectValueFailure is the ready check · dedupe: action door params non-object silently dropped; actions params string array; flow action params shape refusal", "class: a · reach: public door, same boot: POST /api/v1/automation/showcase_notify_owner/trigger with a JSON string body \"oops\" dispatched with an empty bag (400 FLOW_FAILED Unknown variable ownerId), and an array body [1,2] forwarded its indices as params · evidence: buildAutomationContext's ctxBody = body && typeof body === 'object' ? body : {} (an array passes); the resume door refuses a non-object body as (body) invalid_type; the #25821 ruling covers params only, so not fixed here · dedupe: trigger body non-object array dispatched; automation trigger body shape; trigger door body refusal" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Landed
PR #25974 merged at 2026-10-26T08:06Z by the merge queue as
e12d3f3db6onmain(Fixes #25821; ACCEPT6410618469, contract review6410594343).git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-25974-*'origin/mainby contentgit grep -c objectValueFailure origin/main -- packages/runtime/src/domains/automation.tsgit merge-base --is-ancestor e12d3f3db6 origin/mainState change: this card closed by
Fixes(completed);pm:dispatchedremoved. Its two sibling doors (the action door'sparams, the trigger door's body) are #25976, dispatched on top of it (claim6411222289).Generated by Claude Code