Skip to content

Claude subagents: forward a foreground subagent's text and thinking - #1042

Merged
zeronsh[bot] merged 3 commits into
zeronsh:mainfrom
alexandre-vl:subagents/forward-subagent-text
Oct 26, 2026
Merged

zeronsh[bot] merged 3 commits into
zeronsh:mainfrom
alexandre-vl:subagents/forward-subagent-text

Conversation

@alexandre-vl

@alexandre-vl alexandre-vl commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1000.

Stacked on #1041. Until it merges, this diff includes its code commit; new here are the last two (Claude subagents: forward a foreground subagent's text and thinking and its demo media).

A foreground subagent's tab showed its tool calls and nothing else. Unless asked, Claude Code leaves the text and thinking blocks of a foreground subagent, or of a forked skill, out of the stream. Runs now ask for them, and the adapter folds the thinking blocks they bring.

It needs #1041's spawn gate. Once asked, the CLI forwards subagents "at every nesting depth" (Follow subagent messages). Without the gate, the agents a subagent starts would each open a doc no card lists, and the pill would climb again (1 → 3 in the A/B on #1000).

The same foreground subagent's tab. On v0.2.121 it holds the prompt twice and "Ran 1 command". On this branch it also has two thoughts, "The sky is blue." and "The grass is green."

What changes

  • Launch. build_command sets CLAUDE_CODE_FORWARD_SUBAGENT_TEXT=1 (crates/harness/src/claude/mod.rs:349), which "enables the same behavior" as --forward-subagent-text (CLI reference). It's the variable rather than the flag because a CLI older than 2.1.211 ignores the variable, but exits on a flag it doesn't know. A claude started inside the run inherits the variable. Outside stream-json print mode the CLI ignores it "so that nested invocations keep working when it's set process-wide" (environment variables). A nested stream-json claude, such as an Agent SDK script or claude -p --output-format stream-json, forwards its own subagents' text too, which only adds tagged frames to its stream.
  • Adapter. A tagged assistant frame's thinking blocks become the subagent's reasoning, in block order with its text and tool calls (crates/harness/src/claude/normalize.rs:728). A signature-only block is skipped, and each thought ends with a blank line, like text blocks. Text blocks were already folded, but they never arrived. A background subagent sends its thinking unasked (live-2.1.228-background-subagent.jsonl, lines 62 and 72), so its tab gains its reasoning too.

Testing

  • macOS 27.0, rustc 1.99.0, cargo test --locked: -p zeron-harness --lib --test claude --test codex gives 447, 36 and 45 passed, 3 and 4 ignored as on main. -p zeron-doc --lib and the engine suites give the same counts as on Claude subagents: count and open only this chat's spawns #1041.
  • runs_ask_the_cli_to_forward_subagent_text reads the variable off the built command. Without the line it fails with left: None right: Some("1").
  • subagent_assistant_blocks_emit_tagged_reasoning_text_and_calls feeds a signature-only thinking block, two thoughts, a text block and a tool call, and expects exactly "Plan it.\n\n", "Then list.\n\n", the text, the call. Folding the empty block, or keeping a thought's trailing space and no blank line, fails it.
  • captured_live_forwarded_foreground_subagent_replays_in_order replays frames from Claude Code 2.1.310 run with the variable (live-2.1.310-foreground-subagent-forwarded.jsonl). The subagent's events come out as thinking, "The sky is blue.", echo hi, thinking, "The grass is green.", in that order; without the fold, ["sky", "exec", "grass"]. The capture skips the main thread's 79 frames before its Agent call (28.6 KB, mostly partial deltas), which no assertion reads. One thought that named the account's connectors was reworded.
  • captured_live_background_subagent_frames_replay_correctly now also counts the background subagent's two thoughts (2.1.228 capture, lines 62 and 72). Without the fold: left: 0 right: 2.
  • Live, Claude Code 2.1.310 with Haiku, the same prompts on the installed v0.2.122, on Claude subagents: count and open only this chat's spawns #1041 and on this branch. The screenshots' v0.2.121 has the same claude driver and sessions.rs: no commit touches them between v0.2.121 and this branch's base.
Subagent tab v0.2.122 and #1041 This branch
Foreground the prompt twice, exec the prompt twice, reasoning, "The sky is blue.", exec, reasoning, "The grass is green."
Background the prompt, "The sky is blue.", exec, "The grass is green." the prompt, reasoning, "The sky is blue.", exec, reasoning, "The grass is green."

Only this branch sets the variable, and its background run's journal holds each sentence once: the variable doubles nothing.

Known limits

  • A foreground subagent's tab shows its prompt twice, with or without this. Claude: stop doubling a foreground subagent's opening prompt #952 fixes that.
  • A subagent's text arrives as whole blocks, not streamed. Two unedited 2.1.310 captures with the variable set (184 and 314 frames) have 102 and 123 stream_event frames, none of them tagged.
  • Per the same docs, forwarding at every depth needs 2.1.219, and a forked skill's text 2.1.265. Older CLIs keep today's tabs.

Screenshots

Offscreen renders of the production Shell (Window::render_to_image), attached to each engine, with the subagent's tab open; docs/media/forward-subagent-text/README.md says how they were made. The media are in their own commit, 20cddd40, if you'd rather drop them before merging.

v0.2.121 This branch
The subagent's tab on v0.2.121: the prompt twice, then Ran 1 command The subagent's tab on this branch: the prompt twice, Thought process, The sky is blue., Thought process with Ran 1 command, The grass is green.
Full windows
v0.2.121 This branch
v0.2.121, full window This branch, full window

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

The ● N badge counted every tagged stream the engine opened a sink for,
while the lanes card lists only the chat's spawn chips. Claude Code tags
a forked skill's traffic with its Skill call's id, the agents a subagent
starts with their own Agent call ids, and a skill passed as `/<name>`
with `forked-command-<name>`. None of them is a spawn chip of the chat,
so the badge could read 3 over an empty card (zeronsh#999).

A sink (its own doc, one unit on the badge) now opens only for a spawn
chip of the chat (`is_chat_spawn`). Traffic tagged with any other id
stays in the journal.

A forked skill is the chat's own subagent, though. The Claude adapter
sets each Skill call aside until its task starts, which comes ahead of
all the fork's traffic, and re-decodes it then as a spawn named after
the skill (`Agent: /review`). Its traffic then has a card and a tab to
feed. An inline skill starts no task, and its result drops it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude Code leaves the text and thinking blocks of a foreground subagent
(or a forked skill) out of the stream unless asked, so its tab showed
tool calls only (zeronsh#1000). Runs now set CLAUDE_CODE_FORWARD_SUBAGENT_TEXT=1,
which "enables the same behavior" as `--forward-subagent-text`
(https://code.claude.com/docs/en/cli-reference). The variable rather than
the flag: a CLI older than 2.1.211 ignores it, but refuses an unknown flag.

The CLI then forwards subagents "at every nesting depth"
(https://code.claude.com/docs/en/headless). The spawn gate keeps the
agents a subagent starts in the journal, instead of badging the chat
with docs no card lists (zeronsh#999).

The adapter already emitted tagged text blocks. Tagged thinking blocks
now become the subagent's reasoning, in block order. A background
subagent sends them unasked, so its tab gains its reasoning too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@zeronsh
zeronsh Bot force-pushed the subagents/forward-subagent-text branch from f10c848 to e4b6fd7 Compare October 26, 2026 20:54

@zeronsh zeronsh Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security audit clean: Rust and test fixtures only, plus PNG demo media (verified real PNGs). No dependency, build, CI or network changes; the one runtime change sets CLAUDE_CODE_FORWARD_SUBAGENT_TEXT=1 on the claude child. Reviewed the spawn gate (#1041's commit, included here) and the tagged-thinking fold; both look correct. Rebased onto main (98abb77). Locally: harness lib 446, claude 36, codex 45, doc 151, proto 89, engine sessions 22, all passing; clippy and rustfmt add nothing new.

@zeronsh
zeronsh Bot merged commit 978e8da into zeronsh:main Oct 26, 2026
19 checks passed
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.

[Bug]: Claude harness: foreground subagent and forked-skill tabs lose their text and thinking

1 participant