[BUG] Subagent transcripts record streaming-start usage: output_tokens under-reported ~30x, stop_reason null on every chunk

Status Open
Reported on v2.1.292
Maintainer reply None cached
Activity 0 comments · opened Oct 7, 2026

Preflight Checklist

  • [x] I have searched existing issues (search terms: "subagent usage output_tokens", "transcript usage", "sidechain output tokens")
  • [x] This is a single bug report (the cache-TTL docs gap and the classifier observation are filed separately below)
  • [x] I am using the latest version of Claude Code (2.1.292 at the time of writing)

What's Wrong?

In a subagent transcript (~/.claude/projects/<project>/<session-id>/subagents/agent-<name>-<id>.jsonl), every assistant entry carries message.usage.output_tokens of 3 to 9 and message.stop_reason: null. The same message.id appears 3 to 4 times with the same small count. Only the final entry of the whole subagent run has stop_reason: "end_turn" and a real output count. The entries look like the usage block from message_start, repeated per streamed chunk, never replaced by the final usage.

The parent session's own transcript and a headless claude -p session's transcript do not have this problem: each assistant entry carries stop_reason (tool_use or end_turn) and the real output_tokens.

Effect: any tool that computes cost from transcripts (our own usage dashboards, and anything built on the JSONL) sees subagent output as nearly zero. On one four-file build the subagent logged 461 output tokens in total; the same brief run headless logged 15,575. Cache read and cache write counts match between the two modes for the same job (831k read / 79k write headless vs 814k / 96k subagent), so only output is affected. Billing is server-side and unaffected; local accounting is what is wrong. A subagent that is induced to emit large output (for example by injected content) burns the user's allowance without showing up in local accounting until the limit bar moves.

What Should Happen?

Each subagent assistant entry should record the final usage for that message (as the parent transcript does), or the documentation should state that subagent transcript usage is partial and must not be summed.

Error Messages/Logs

# subagent (Sonnet 5.5), one message id logged 4 times; output_tokens per assistant entry over the whole run:
5 5 5 5 3 3 3 8 8 8 8 8 9 9 3 3 3 8 8 8 32 32 3 3 8 8 8 374
# stop_reason over the same entries: 27 x null, 1 x end_turn

# second subagent (Sonnet 5.5), output_tokens/stop_reason per entry:
16/null 16/null 16/null 6/null 6/null 3/null 3/null 9/null 9/null 3/null 3/null 3/null 3/null 2/null 2/null 40/null 40/null 7/null 7/null 331/end_turn

# the same brief as a headless `claude -p` session (Sonnet 5.5), output_tokens per entry:
339 339 339 339 360 360 282 282 188 4618 4618 3278 3278 659 659 659 2716 2716 892 892 1110 1110 687 687 446

# per-session sums (unique message ids; same job, same brief, same model):
headless-single   req=12  in=24  out=15575  cache_read=831142  cw_5m=0      cw_1h=78655
subagent          req=11  in=22  out=461    cache_read=814287  cw_5m=96162  cw_1h=0

# parent session transcript, stop_reason counts (for comparison; no null entries):
68 end_turn, 256 tool_use

Steps to Reproduce

  1. In an interactive session, launch one subagent with the Agent tool, model Sonnet, with a prompt that makes it write a few files and run a test (any brief that takes 5 to 10 tool calls).
  2. When it returns, open ~/.claude/projects/<project>/<session-id>/subagents/agent-*.jsonl and run:

``
jq -r 'select(.type=="assistant") | "\(.message.id) \(.message.usage.output_tokens) \(.message.stop_reason)"' agent-*.jsonl
`
Observe repeated ids, output_tokens in single digits,
stop_reason` null on all but the last line.

  1. Run the same prompt headless: claude -p "<same prompt>" --model sonnet --output-format json, take session_id from the result, open ~/.claude/projects/<project>/<session_id>.jsonl, run the same jq. Observe real output counts and stop reasons on every entry.
  2. Sum output_tokens over unique message.id in both files and compare.

Claude Model: Not sure / Multiple models. Parent session on Fable 5.1 (claude-fable-5-1); subagents on Sonnet 5.5 (claude-sonnet-5-5) and Haiku 4.5 (claude-haiku-4-5-20251001); the headless comparison runs on Sonnet 5.5. The under-count appears on every subagent regardless of model.

Is this a regression? I don't know. Last Working Version: unknown.

Claude Code Version: 2.1.292 (Claude Code)

Platform: Anthropic API (Claude subscription, auto permission mode)

Operating System: macOS 26.7.1 (build 25G241), Apple Silicon (arm64)

Terminal: iTerm.app 3.6.10

Additional Context

  • Effort level: high (CLAUDE_EFFORT=high). Permission mode: auto. Bun 1.3.13.
  • Observed on 23 subagents across one evening (12 build workers, 10 identical control copies, one 3-stage pipeline); every subagent transcript shows the pattern.
  • Subagents write 5-minute cache (cache_creation.ephemeral_5m_input_tokens); headless and interactive sessions here write 1-hour cache. That is a separate documentation question (Issue 2 below), noted here only because it is the other difference between the two transcript kinds.
  • Full logs available on request; they contain no secrets. A chronicle of the measurements is in my fork of PAI under Plans/.

View original on GitHub ↗