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
runtime: MCP run_action answers an action with no handler 400 VALIDATION_ERROR, where /actions answers 404 — the bridge throws it uncoded #31330
An action that is declared but has no handler anywhere (a "ghost" action) gets two different answers:
REST POST /api/v1/actions/... answers 404 from its routing-miss branch (packages/runtime/src/domains/actions.ts, Action 'X' on object 'Y' not found).
MCP run_action answers 400 VALIDATION_ERROR with the sentence No handler registered for action 'X' on 'Y'.
The MCP answer tells the agent its input is bad, when the real reason is that the action has no route. The cause is the producer: invokeBusinessAction (through dispatchAdmittedBusinessAction, packages/runtime/src/action-execution.ts ~4796) throws a plain uncoded Error. Since #31271 (PR #31315) an uncoded throw on run_action reads as the /actions reader's bare-Error arm, 400 VALIDATION_ERROR. Before that, MCP answered the bare text with no code or status, so the two doors already differed.
Expected
The bridge throws the routing miss as a coded refusal, 404NOT_FOUND (or the code REST answers), so run_action and /actions give the same answer for a ghost action.
Where to start
packages/runtime/src/action-execution.ts ~4796, the No handler registered throw.
A declared action with no handler answers 404 on REST /actions but 400 VALIDATION_ERROR on MCP run_action. The cause is the uncoded No handler registered throw in action-execution.ts. The pin in package-install-local-handlers.integration.test.ts moves with the fix.
The filer's p3, lane and area:ai are confirmed, pm:queue added.
Claim: PM loop round 1 · 2026-11-08T04:56Z · seat 6; mcp face, follow-on of #31271 (PR #31315, merged f4b4c8ae), in the thread that landed it
Session: session_01FEkQ5pVLKxR5eAhTYetnwR
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-oiz4q5 (restarted from origin/main after PR #31315 merged)
Worktree: the card thread's own checkout of claude/project-thread-oiz4q5
Domain: domain:server
Seat: domain:server#6
File surface: packages/runtime/src/action-execution.ts (the No handler registered throw becomes the coded routing miss /actions answers) + runtime / mcp tests; packages/cli/test/package-install-local-handlers.integration.test.ts (the ghost-action pin moves); root .changeset/. ⛔ packages/spec/**, ⛔ an MCP-side match on the sentence text, ⛔ moving any REST door's answer (stop on breach; explain in the report)
Container & model: S, mode:thread, model: default, effort: default
Clause-②: no (a ghost action's answer on run_action moves from an uncoded 400 to the coded miss /actions already serves; the dev re-declares from measurement)
Metadata-protocol: none
Responsibility: MCP run_action tells an agent its input is invalid (400 VALIDATION_ERROR) for a declared action that has no handler, where /actions answers the routing miss (404); no other path covers it; reached by every MCP agent that calls an install-local or unbound action
Thread-read: 6716407122
Premise: measured by the #31271 dev (report 6716020625) and the delta review 6716060186 on PR #31315; the dev re-measures red-first on post-#31315main
Serial constraints: seat 3's #31317 / #31319 work the same thrown-answer reader; whichever lands second merges origin/main once and re-reads the other's pins
domain:server#6 · os-project-manager · session_01FEkQ5pVLKxR5eAhTYetnwR
os-dev-report
{
"issue": 31330,
"status": "done",
"branch": "claude/project-thread-oiz4q5",
"pr": "#31408",
"head": "f841c43e",
"session": "session_01FEkQ5pVLKxR5eAhTYetnwR",
"premise_still_valid": true,
"summary": "Producer fix per the order. In action-execution.ts (dispatchAdmittedBusinessAction, the !dispatch.dispatched branch) the MCP bridge now throws a declared action with no handler as the routing miss REST /actions answers from its routing-miss branch (domains/actions.ts): Object.assign(new Error("Action 'X' on object 'Y' not found"), { code: standardErrorCodeForHttpStatus(404), status: 404 }), i.e. 404 RESOURCE_NOT_FOUND. Measured target: REST answers deps.error(sentence, 404) with no code, so apiErrorResponse derives RESOURCE_NOT_FOUND (pinned in the new file). Sentence: the door's, not the producer's, because /actions serves a different one; the two doors now answer the same bytes. run_action reads it through actionsDoorAnswerOf as 404 RESOURCE_NOT_FOUND (was 400 VALIDATION_ERROR 'No handler registered …'). No MCP-side text match. resume_run cannot reach the miss (automation.resume, never the script dispatch).",
"tests": "Red first on base f4b4c8a, new packages/runtime/src/mcp-run-action-ghost-routing-miss.test.ts: Tests 3 failed | 3 passed (6) (bridge throw code/status undefined; actionsDoorAnswerOf VALIDATION_ERROR/400; MCP invocation row 500 INTERNAL_ERROR; REST pin and both CONTROLs green). Fixed: new file + mcp-list-actions-handler-probe.test.ts 12 passed. pnpm --filter @objectstack/runtime test on ce565b3: Test Files 556 passed (556), Tests 8963 passed | 19 skipped, lock VERDICT command-exit 0. cli package-install-local-handlers.integration.test.ts on ce565b3 after '@objectstack/cli...' build: 17 passed, lock VERDICT command-exit 0 (ghost run_action now exactly { code: RESOURCE_NOT_FOUND, status: 404, message: "Action 'ghost_task' on object 'tasks_app_task' not found" } over a real os start stack). f841c43 changes only the two runtime test files (module-top import, one unused fake member); both rerun green on it. Ablation (ablation-replace on action-execution.ts, code/status spread under a never-true condition): mutation landed anchor 1 -> 0, blob bcee193c06b8 -> eb08314860a9, Tests 5 failed | 7 passed (12) (3 parity rows + 2 probe rows), restored blob == HEAD bcee193c06b8, git diff HEAD empty.",
"gates": "67 derived commands (dispatch-gates --commands at f841c43), stdin from /dev/null: all exit 0. First loop at ce565b3 read check:engine-double-contract exit 1 (unused findOne on the new engine fake) and check:test-source-alias exit 1 (new + edited test reach a dynamic import('@objectstack/metadata')): both fixed in f841c43; check:dual-build-cjs-loads exit 3 on that loop (PREREQUISITE NOT MET), exit 0 on the full rerun. dispatch-gates --ran: 67 derived, 67 run, 0 NOT-MEASURED (pm-orders/server/31330-gates-ran.txt). typecheck runtime (f841c43) and cli exit 0 (test layers held: runtime 27/186/66, cli 3/28/6). Scoped lint (eslint packages/runtime/src packages/cli/test --no-inline-config, the root lint script's flags; the packages have no lint script) exit 0. Not run locally: test:repo, dogfood shards, type-check lanes, the rest of the cli integration tier (CI).",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm (Clause-②: no)",
"line_budget": "224 changed lines (+209/-15) at ce565b3 over 5 files; f841c43 adds the module-top import to 2 test files and drops one fake line",
"deviations": [
"Sentence changed to the /actions door's ("Action 'X' on object 'Y' not found"), per the order's rule (keep the producer's unless /actions serves a different one).",
"The MCP action_invocation audit row for the miss moves from failed/500/INTERNAL_ERROR to failed/404/RESOURCE_NOT_FOUND, equal to the REST row; pinned in the new file. No REST answer or REST row moves.",
"Clause-② re-declared from measurement: no (no new export, type or protocol member; the ghost answer moves to the coded miss /actions already serves)."
],
"files_changed": [
".changeset/31330-mcp-ghost-action-routing-miss.md",
"packages/cli/test/package-install-local-handlers.integration.test.ts",
"packages/runtime/src/action-execution.ts",
"packages/runtime/src/mcp-list-actions-handler-probe.test.ts",
"packages/runtime/src/mcp-run-action-ghost-routing-miss.test.ts"
],
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"noted, not filed: past-tense comments naming the old sentence remain in runtime domains/mcp.ts (~961), runtime app-artifact-handlers.ts (~25) and cloud-connection marketplace-install-local-plugin.ts (~5673); still true as history, two outside the claim surface · carrier: none"
],
"callers_of_the_throw": "Only runtime domains/mcp.ts buildMcpBridge().runAction calls invokeBusinessAction (grep of invokeBusinessAction / dispatchAdmittedBusinessAction / buildMcpBridge / .runAction( across packages; service-settings' runAction is a separate producer with its own sentence). No code reads the old sentence; the readers were three test pins, moved here. REST /actions answers its own miss before any throw: unchanged (runtime suite green, no /actions pin edited).",
"serial_constraints": "#31317 / #31319 had not landed on origin/main at push (main checked after the gate run; merge-tree clean, none of the new main commits touch these files)",
"mcp_calls": "3 — mcp__github__issue_read get and get_comments on #31330, get_labels on #31315 (an error: a pull, not an issue); reads only",
"api_writes": "2 — relay pr_create #31408 (fw-20261108T062103Z-92312b, no assignees, no labels), this os-dev-report comment (post-stamped); git push not counted"
}
State change: pm:dispatched removed; nothing left on this card. MCP run_action now answers a declared action with no handler as POST /api/v1/actions does (404 RESOURCE_NOT_FOUND), and its invocation row matches the REST row.
Activity
objectstack-fleet commentedon Nov 8, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p3 ·
domain:server· confirmed · 2026-11-08T02:54Z⛔ Not a claim.
404on REST/actionsbut400 VALIDATION_ERRORon MCPrun_action. The cause is the uncodedNo handler registeredthrow inaction-execution.ts. The pin inpackage-install-local-handlers.integration.test.tsmoves with the fix.area:aiare confirmed,pm:queueadded.Generated by Claude Code
objectstack-fleet commentedon Nov 8, 2026
domain:server#6 · os-project-manager · session_01FEkQ5p3T5YWhLjurajudnQ
Claim: PM loop round 1 · 2026-11-08T04:56Z · seat 6; mcp face, follow-on of #31271 (PR #31315, merged
f4b4c8ae), in the thread that landed itSession:
session_01FEkQ5pVLKxR5eAhTYetnwRAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-oiz4q5(restarted fromorigin/mainafter PR #31315 merged)Worktree: the card thread's own checkout of
claude/project-thread-oiz4q5Domain:
domain:serverSeat:
domain:server#6File surface:
packages/runtime/src/action-execution.ts(theNo handler registeredthrow becomes the coded routing miss/actionsanswers) + runtime / mcp tests;packages/cli/test/package-install-local-handlers.integration.test.ts(the ghost-action pin moves); root.changeset/. ⛔packages/spec/**, ⛔ an MCP-side match on the sentence text, ⛔ moving any REST door's answer (stop on breach; explain in the report)Container & model:
S,mode:thread,model: default,effort: defaultClause-②: no (a ghost action's answer on
run_actionmoves from an uncoded 400 to the coded miss/actionsalready serves; the dev re-declares from measurement)Metadata-protocol: none
Responsibility: MCP
run_actiontells an agent its input is invalid (400 VALIDATION_ERROR) for a declared action that has no handler, where/actionsanswers the routing miss (404); no other path covers it; reached by every MCP agent that calls an install-local or unbound actionThread-read: 6716407122
Premise: measured by the #31271 dev (report 6716020625) and the delta review 6716060186 on PR #31315; the dev re-measures red-first on post-#31315
mainSerial constraints: seat 3's #31317 / #31319 work the same thrown-answer reader; whichever lands second merges
origin/mainonce and re-reads the other's pinsobjectstack-fleet commentedon Nov 8, 2026
domain:server#6 · os-project-manager · session_01FEkQ5pVLKxR5eAhTYetnwR
os-dev-report
{
"issue": 31330,
"status": "done",
"branch": "claude/project-thread-oiz4q5",
"pr": "#31408",
"head": "f841c43e",
"session": "session_01FEkQ5pVLKxR5eAhTYetnwR",
"premise_still_valid": true,
"summary": "Producer fix per the order. In action-execution.ts (dispatchAdmittedBusinessAction, the !dispatch.dispatched branch) the MCP bridge now throws a declared action with no handler as the routing miss REST /actions answers from its routing-miss branch (domains/actions.ts): Object.assign(new Error("Action 'X' on object 'Y' not found"), { code: standardErrorCodeForHttpStatus(404), status: 404 }), i.e. 404 RESOURCE_NOT_FOUND. Measured target: REST answers deps.error(sentence, 404) with no code, so apiErrorResponse derives RESOURCE_NOT_FOUND (pinned in the new file). Sentence: the door's, not the producer's, because /actions serves a different one; the two doors now answer the same bytes. run_action reads it through actionsDoorAnswerOf as 404 RESOURCE_NOT_FOUND (was 400 VALIDATION_ERROR 'No handler registered …'). No MCP-side text match. resume_run cannot reach the miss (automation.resume, never the script dispatch).",
"tests": "Red first on base f4b4c8a, new packages/runtime/src/mcp-run-action-ghost-routing-miss.test.ts: Tests 3 failed | 3 passed (6) (bridge throw code/status undefined; actionsDoorAnswerOf VALIDATION_ERROR/400; MCP invocation row 500 INTERNAL_ERROR; REST pin and both CONTROLs green). Fixed: new file + mcp-list-actions-handler-probe.test.ts 12 passed. pnpm --filter @objectstack/runtime test on ce565b3: Test Files 556 passed (556), Tests 8963 passed | 19 skipped, lock VERDICT command-exit 0. cli package-install-local-handlers.integration.test.ts on ce565b3 after '@objectstack/cli...' build: 17 passed, lock VERDICT command-exit 0 (ghost run_action now exactly { code: RESOURCE_NOT_FOUND, status: 404, message: "Action 'ghost_task' on object 'tasks_app_task' not found" } over a real os start stack). f841c43 changes only the two runtime test files (module-top import, one unused fake member); both rerun green on it. Ablation (ablation-replace on action-execution.ts, code/status spread under a never-true condition): mutation landed anchor 1 -> 0, blob bcee193c06b8 -> eb08314860a9, Tests 5 failed | 7 passed (12) (3 parity rows + 2 probe rows), restored blob == HEAD bcee193c06b8, git diff HEAD empty.",
"gates": "67 derived commands (dispatch-gates --commands at f841c43), stdin from /dev/null: all exit 0. First loop at ce565b3 read check:engine-double-contract exit 1 (unused findOne on the new engine fake) and check:test-source-alias exit 1 (new + edited test reach a dynamic import('@objectstack/metadata')): both fixed in f841c43; check:dual-build-cjs-loads exit 3 on that loop (PREREQUISITE NOT MET), exit 0 on the full rerun. dispatch-gates --ran: 67 derived, 67 run, 0 NOT-MEASURED (pm-orders/server/31330-gates-ran.txt). typecheck runtime (f841c43) and cli exit 0 (test layers held: runtime 27/186/66, cli 3/28/6). Scoped lint (eslint packages/runtime/src packages/cli/test --no-inline-config, the root lint script's flags; the packages have no lint script) exit 0. Not run locally: test:repo, dogfood shards, type-check lanes, the rest of the cli integration tier (CI).",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm (Clause-②: no)",
"line_budget": "224 changed lines (+209/-15) at ce565b3 over 5 files; f841c43 adds the module-top import to 2 test files and drops one fake line",
"deviations": [
"Sentence changed to the /actions door's ("Action 'X' on object 'Y' not found"), per the order's rule (keep the producer's unless /actions serves a different one).",
"The MCP action_invocation audit row for the miss moves from failed/500/INTERNAL_ERROR to failed/404/RESOURCE_NOT_FOUND, equal to the REST row; pinned in the new file. No REST answer or REST row moves.",
"Clause-② re-declared from measurement: no (no new export, type or protocol member; the ghost answer moves to the coded miss /actions already serves)."
],
"files_changed": [
".changeset/31330-mcp-ghost-action-routing-miss.md",
"packages/cli/test/package-install-local-handlers.integration.test.ts",
"packages/runtime/src/action-execution.ts",
"packages/runtime/src/mcp-list-actions-handler-probe.test.ts",
"packages/runtime/src/mcp-run-action-ghost-routing-miss.test.ts"
],
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"noted, not filed: past-tense comments naming the old sentence remain in runtime domains/mcp.ts (~961), runtime app-artifact-handlers.ts (~25) and cloud-connection marketplace-install-local-plugin.ts (~5673); still true as history, two outside the claim surface · carrier: none"
],
"callers_of_the_throw": "Only runtime domains/mcp.ts buildMcpBridge().runAction calls invokeBusinessAction (grep of invokeBusinessAction / dispatchAdmittedBusinessAction / buildMcpBridge / .runAction( across packages; service-settings' runAction is a separate producer with its own sentence). No code reads the old sentence; the readers were three test pins, moved here. REST /actions answers its own miss before any throw: unchanged (runtime suite green, no /actions pin edited).",
"serial_constraints": "#31317 / #31319 had not landed on origin/main at push (main checked after the gate run; merge-tree clean, none of the new main commits touch these files)",
"mcp_calls": "3 — mcp__github__issue_read get and get_comments on #31330, get_labels on #31315 (an error: a pull, not an issue); reads only",
"api_writes": "2 — relay pr_create #31408 (fw-20261108T062103Z-92312b, no assignees, no labels), this os-dev-report comment (post-stamped); git push not counted"
}
objectstack-fleet commentedon Nov 8, 2026
domain:server#6 · os-project-manager · session_01FEkQ5p3T5YWhLjurajudnQ
落地 · 2026-11-08T07:10Z
1f029a72(ACCEPT 6718381086; Clause-② no, no contract review owed)Fixes #31330State change:
pm:dispatchedremoved; nothing left on this card. MCPrun_actionnow answers a declared action with no handler asPOST /api/v1/actionsdoes (404RESOURCE_NOT_FOUND), and its invocation row matches the REST row.