fix(server): a resumed Claude subagent's thread shows the message that resumed it - #13668
Conversation
…t resumed it SendMessage to a finished Claude subagent re-emits task_started for the same task id under the SendMessage call's tool_use_id, with the sent text as the prompt. The adapter only wrote a child-thread prompt for the launch, so the resumed run's reply appeared with no user message before it. Track the tool call that started the current run; a task_started under a different one emits its own user message, keyed on that tool_use_id and placed after the previous run. The launch prompt keeps its id. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a localized Claude adapter bug fix that adds the missing resume prompt to an existing subagent thread while preserving initial-launch and replay behavior. The targeted fixture verifies the expected prompt/reply ordering, with no schema, security, infrastructure, or default changes. You can add or adjust custom eligibility rules. Learn more. |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: unavailable · PR result: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
a4cd403
into
t3code/codex-turn-mapping
When a Claude parent resumes a finished subagent with SendMessage, the CLI re-emits
task_startedfor the sametask_id. That frame carries the SendMessage call'stool_use_idand uses the sent text asprompt. The adapter only wrote a child-thread user message when a task first appeared, so the resumed run's reply showed up in the child thread with no message before it.What changed
ActiveClaudeSubagentnow recordsrunToolUseId, the tool call that started the current run. Atask_startedfor a known task under a differenttool_use_idis a resume. It emits its own user message:task:<id>:prompt:<toolUseId>senderThreadIdattribution ("Sent by another agent")The launch prompt keeps its existing id (
task:<id>:prompt, ordinal 100), so existing threads and fixtures are unchanged. A drain-replayed or duplicatetask_startedunder the same tool call emits nothing new.Verification
claude_background_subagent_lifecyclealready contains a SendMessage resume of Agent A. Its output assertion now checks the child thread in display order:user:A_FIRST prompt, assistant:A_FIRST, user:A_SECOND prompt, assistant:A_SECOND.[user:Reply with exactly: A_FIRST, assistant:A_FIRST, assistant:A_SECOND], with no resume prompt.vp test run src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t claude: 26 passed.vp test run src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts: 118 passed.vp exec tsc --noEmit -p .inapps/server: no errors or warnings.vp run knip:check: clean.vp linton the changed files: only a pre-existing unusedlayerwarning at the end ofClaudeAdapterV2.ts, which this PR does not touch.Not covered here
In the live session that surfaced this, the original task text was replaced rather than missing. That happens on a different path. The server restarted between the launch and the SendMessage, so the adapter's in-memory subagent registry was empty. The resume
task_startedthen looked like a brand-new task and rewrote the launch prompt's message id with the new text. This PR does not change that path. It needs its own fix and a recording with a session reopen between launch and resume.Codex does not have this bug. It keys each child-thread user message on the native
userMessageitem id, and thesubagent_continuefixture already asserts both prompts.Model: Claude Opus 5.5 (Claude Code)
🤖 Generated with Claude Code