Skip to content

fix(mcp): inbox relay messages say the run's state and why it stopped - #95

Open
chrisohearn wants to merge 3 commits into
integration/mcp-events-20261109from
r4/w2-relay-stop-reason
Open

chrisohearn wants to merge 3 commits into
integration/mcp-events-20261109from
r4/w2-relay-stop-reason

Conversation

@chrisohearn

@chrisohearn chrisohearn commented Nov 13, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to #93 (merged as 7eddc7f). This is review P3 #1. Ledger key r4/w2-restarts. No migration.

Problem. Webhook and replay deliveries carry each event's state, plus stopReason for runs (#84, #93). Messages relayed to a pinned inbox (McpEventRelay collect) carried neither. On the inbox route, Jarvis therefore couldn't tell a restart cancel (server_shutdown / server_restart) from a real cancel without reading the source thread.

Change. A new relayEventState(event) adds the following to the relay body:

  • state: the run status, the request status, or detached;
  • stopReason: for a cancelled or interrupted run that recorded one, the same value the webhook data carries. A rolled-back run that still holds an earlier stop gets none, as in McpEvents.observeEvent.

Replay bodies already embed the full event data, so they are unchanged.

Failover runs (coordinator's decision, same PR). The inbox relay now matches observeEvent and the webhook path:

  • The run failing over is skipped. A run.updated with a failover in progress (not skipped) queues no message. Before, the inbox got a false thread.blocker with state: "failed" for a run that was actually moving to the fallback model.
  • The failover run's result is relayed. A binding tracks one run, and the failover run has a new id. So the relay now follows a run whose failoverOfRunId is the bound run, including chained failovers, and relays that run's result. It saves the followed run id on the binding as run_id. The binding id stays the hash of the original run.
  • A skipped failover still reports a blocker.

Tests.

  • McpEvents.test.ts, existing inbox relay case: the relayed body now asserts state: "failed" and no stopReason.
  • McpEvents.test.ts, new relay case: a run fails with a failover pending, then dispatched to a fallback run, which completes. The inbox gets one thread.run.result for the fallback run (state: "completed") and no blocker. This fails on the previous relay, which sent the blocker.
  • McpEvents.test.ts, existing stop-reason case: relayEventState gives server_shutdown for a restart cancel, no stopReason for a cancel without a stop, and none for a rolled-back run.
  • Under the round-four resource rule, I ran only that file and a server-package typecheck locally, under the shared lock. Hosted CI is the gate.

Acceptance (read-only, after deploy). The next restart that cancels a relay-bound run puts a [T3 event relay] message in Jarvis's inbox. Its JSON has "state":"cancelled","stopReason":"server_shutdown" (or server_restart). To check from the database:

sqlite3 -readonly ~/.t3/userdata/statev2.sqlite "select substr(json_extract(payload_json,'\$.text'),1,400)
  from orchestration_v2_projection_messages
  where message_id like 'message:mcp-relay:%' order by created_at desc limit 3"

Not run: the mcp-tool-use eval. This only adds fields to a model-facing relay message, and the eval calls paid models.

🤖 Generated with Claude Code

@chatgpt-codex-connector

Copy link
Copy Markdown
Codex Review Summary: ❌ Failed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ❌ Failed 2026-11-13T04:36:22.687206Z
We couldn’t complete this review. Comment @codex review to try again.
dae29a0 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@chrisohearn

Copy link
Copy Markdown
Owner Author

Review of record: independent Claude Opus review of dae29a053b

Codex code review failed with "We couldn't complete this review" (usage limit), so under the fallback rule this read-only review is the review of record. The reviewer read git diff 7eddc7fc51 dae29a053b under the round-four resource rule, which allows reading code only. It found nothing blocking.

Consistency with webhook data (McpEvents.observeEvent). States match. The rolled-back rule matches: no stopReason once a run is rolled back. Only three event types reach the relay body: run outcomes, pending runtime requests, and session detaches.

Safety. No new risk:

  • sourceKey dedup and the content hash never read the body, so they are unaffected.
  • Loop protection keys on the message-id prefix, which is unchanged.
  • No server code parses the relay body.
  • stop.reason is only ever written from the server's own OrchestrationV2RunStopReason values, so it carries no client text.

Tests. The change extends existing cases at the lowest layer.

Nit, fixed in d300b7b173: an interrupted + stop row now covers the other branch of relayEventState.

Noted outside this PR. The relay and webhook paths already disagreed on failover runs before this change. observeEvent skips a failed run that is failing over; the relay still queues a thread.blocker for it, which now says state: "failed". I've reported this to the coordinator as a separate follow-up.

Accepted. The mcp-tool-use eval was not run. The change only adds fields to a model-facing relay message, and the eval calls paid models.

🤖 Generated with Claude Code

rallymewrx and others added 3 commits November 12, 2026 22:10
Webhook event data carries state and stopReason, but the inbox relay body
did not, so Jarvis could not tell a restart cancel from a real one on the
inbox route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a blocker

A run that fails over to a fallback model is not a blocker; webhooks and
observeEvent already skip it. The relay queued thread.blocker/failed for it
and then ignored the failover run, which has a new id. It now skips the
failing-over run and follows the run that continues it (and that run's own
failover), so the inbox gets the failover run's result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chrisohearn
chrisohearn force-pushed the r4/w2-relay-stop-reason branch from 5e1958e to 2798f5d Compare November 13, 2026 05:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants