Skip to content

fix(server): keep Claude subagent output in the subagent's thread - #13347

Merged
juliusmarminge merged 3 commits into
pingdotgg:t3code/codex-turn-mappingfrom
scratchyone:fix/claude-subagent-text-routing
Sep 24, 2026
Merged

juliusmarminge merged 3 commits into
pingdotgg:t3code/codex-turn-mappingfrom
scratchyone:fix/claude-subagent-text-routing

Conversation

@scratchyone

@scratchyone scratchyone commented Sep 24, 2026 •

Copy link
Copy Markdown

When Claude runs a subagent through its built-in Agent tool, the subagent's narration, final report, and often its tool calls show up in the parent thread. It's worst with background subagents: once the parent's turn ends, the parent chat fills with another agent's work, while the subagent's own thread shows only a few commands and a result.

Claude sends subagent output in the parent's stream, tagged with parent_tool_use_id. ClaudeAdapterV2 handled that tag in three places, each wrong in a different way:

  • Assistant messages ignored the tag, so subagent text always went to the parent.
  • Tool calls found their subagent through a map that belongs to the current turn. After the parent's turn ends, background subagent output is buffered and replayed into a new turn whose map is empty, so those tool calls went to the parent too.
  • Output that arrived before the subagent's task_started had nowhere to go.

Fix

  • Session-wide lookup: a session-lived index maps tool-use id → subagent, resolved through the session registry, which always holds the current subagent entry. Tool calls, text, and model snapshots all use it.
  • Text routing: subagent assistant text now goes into the subagent's thread.
  • Early output: output that arrives before its subagent is known is held, then replayed as soon as any task_started, task_progress, or task_notification frame names the subagent. Frames are replayed before a notification is handled, so they land ahead of the result. Replayed frames skip turn-level bookkeeping (retry recovery, message cursor), which already ran when they first arrived.
  • Subagents started while the parent is idle: their task_started is now kept in the wake buffer, so their output has an owner when the buffer is drained.
  • Result dedupe: the subagent's final message is shown once. The separate result message is skipped when it matches the last message (content blocks with the same native message id are joined first). Failed and cancelled results always show.

Subagents started through T3's delegate_task weren't affected, since they run in their own sessions.

Testing

Transcript replay: a new replay fixture, claude_background_subagent_after_root, replays a recorded Claude session in which a background subagent narrates and runs commands after the root turn ends, then wakes the root. Its output reaches the adapter through the wake buffer and drains into the continuation run, the path where it leaked. The fixture fails on the previous adapter (SUB_STEP_1 leaked into the parent thread) and passes with this fix.

Replay fixtures previously could not reach that path: the replay runtime never ran the continuation worker, so wake requests were dropped and no fixture got past its first run. A separate commit adds an opt-in runContinuationWorker flag that wires in the production worker (off for every other fixture), lets the Claude recorder capture the wake turn, and lets await_run_status also wait for a turn item.

Adapter tests cover the ordering cases a recording can’t pin down reliably:

  • a subagent started while the parent is idle
  • output that arrives before task_started
  • a tool-use id first named by the notification
  • a replay that must not resolve a newer API retry
  • a final message split across content blocks
  • a subagent update without a tool-use id

Each fails when its part of the fix is reverted. All replay fixtures, the replay contract tests, the Claude adapter suites, and the other harness consumers pass, and the server typecheck is clean.

Checked in a real client with Claude Opus 5.5, one background subagent running four steps.

Before:

before.mp4

After:

after.mp4

Model: Claude Opus 5.5 (Claude Code), reviewed by GPT-6 Astra (Codex)

🤖 Generated with Claude Code

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 24, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The production adapter changes existing Claude subagent routing across live turns, background wake drains, lifecycle events, retries, and result rendering, backed by substantial new state and replay logic. Although the added tests cover several race scenarios, the cross-turn runtime impact and implementation complexity warrant human review.

You can add or adjust custom eligibility rules. Learn more.

@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Sep 24, 2026
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 1bd44f2 to 3b9c885 Compare September 24, 2026 04:06
scratchyone and others added 3 commits September 23, 2026 22:55
Claude forwards native Agent subagent output into the parent stream tagged
with parent_tool_use_id. ClaudeAdapterV2 routed assistant text to the parent
regardless, resolved tool calls through a per-turn map that is empty when
background output drains into a wake turn, and had nowhere to put frames
that arrive before task_started.

Resolve subagents through a session-wide tool-use index, route subagent
text into the child thread, hold early frames until a lifecycle frame names
their owner, keep idle task_started frames for the wake drain, and show a
completed subagent's result once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Replay fixtures could not reach the continuation path: the replay runtime
never ran the ProviderContinuationService worker, so every wake request an
adapter offered was dropped and no fixture got past its first run.

Add an opt-in runContinuationWorker flag that shares one continuation
queue between the adapters, the orchestrator, and the production worker,
as runtimeLayer.ts does. It is off by default, so existing fixtures are
unchanged. Also let the Claude recorder capture the wake turn Claude starts
after background work finishes, labelling its result so a replay gate can
hold it, and let await_run_status also wait for a turn item.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…oot turn

Record a real Claude session where a background Agent subagent narrates
and runs commands after the root turn settles, then wakes the root. Its
frames all reach the adapter through the wake buffer and drain into the
continuation run, the path where subagent output leaked into the parent.
The fixture asserts the parent thread holds only the root's own replies
and the child thread holds the subagent's narration, commands, and single
final report. It fails on the previous adapter and replaces the hand-built
adapter test for the same scenario.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@juliusmarminge
juliusmarminge force-pushed the fix/claude-subagent-text-routing branch from cc40203 to 205f4db Compare September 24, 2026 06:02
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Sep 24, 2026
@juliusmarminge
juliusmarminge merged commit f9f49b7 into pingdotgg:t3code/codex-turn-mapping Sep 24, 2026
22 checks passed
juliusmarminge pushed a commit that referenced this pull request Sep 24, 2026
…3347)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
juliusmarminge pushed a commit that referenced this pull request Sep 24, 2026
…3347)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
juliusmarminge pushed a commit that referenced this pull request Sep 25, 2026
…3347)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants