[BUG] Subagent transcripts often never receive final cumulative `usage` (main-session transcripts always do) — token accounting from transcripts undercounts subagent output by up to ~2/3

Status Open
Reported on v2.1.177
Maintainer reply None cached
Activity 2 comments · opened Aug 5, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

In session transcript JSONL files, assistant records are written per content
block with an early snapshot usage (stop_reason: null, output_tokens
often 1), and on request completion a record with the final cumulative usage
(non-null stop_reason, iterations array, server_tool_use) is
written/backfilled.

  • Main session files: the final usage is backfilled onto every record of the

requestId. Observed 0 misses across 160+ requests.

  • Subagent files (<session>/subagents/agent-*.jsonl and

subagents/workflows/wf_*/agent-*.jsonl): ~20% of requests never get any
final-usage record
— every record for the requestId keeps the early
snapshot (stop_reason: null, no iterations), so the request's true output
token count is unrecoverable from the transcript.

Reproduced on 2.1.177 and 2.1.222 (linux x64, native build, VS Code extension
entrypoint and headless -p alike; Agent-tool subagents and workflow subagents
alike). The loss is race-like: it affects normally-completing agents, first or
middle requests of a file (a later request in the same file can be finalized
fine), and sequential and parallel spawns at similar rates.

Impact: where a final record exists, its usage matches harness-side metering
exactly (verified repeatedly against the Workflow budget.spent() API). Where
it is lost, the banked snapshot is near zero: measured examples — a
two-tool-call request recorded 65 of 250 real output tokens; a
StructuredOutput request recorded 4 of ~112; trivial text replies record 1.
Because thinking tokens appear only in the final cumulative usage (and
thinking blocks are written with empty thinking text in subagent files),
reasoning-heavy subagent workloads lose the most: on one real subagent-heavy
corpus, transcripts held only ~1/3 of harness-metered output tokens even
though only ~8% of requests lost their final. Any tool that accounts usage
from transcripts (per-request max of usage.output_tokens) therefore
systematically undercounts subagent work.

What Should Happen?

Subagent transcripts should backfill the final cumulative usage per requestId
the same way main-session transcripts do (or at least flush the final-usage
record before the agent's transcript file is closed). Every completed API
request in a subagent file should end up with at least one record carrying the
final cumulative usage and a non-null stop_reason.

Error Messages/Logs

No errors are emitted — the failure is silent. Example of an affected agent
file: the subagent completed normally and returned its result to the parent,
but its only assistant record still carries the initial stream snapshot:


{"type":"assistant","requestId":"req_011CdjuJFP4DcU9LBfam","message":{"content":[{"type":"text","text":"plum-4"}],"stop_reason":null,"usage":{"input_tokens":2600,"cache_creation_input_tokens":5325,"cache_read_input_tokens":0,"output_tokens":1,"service_tier":"standard"}}}


A healthy sibling agent from the same run, for comparison, gets a finalized
record (`stop_reason` set, `iterations` present, correct `output_tokens`):


{"type":"assistant","requestId":"req_011CdjuJXh5pEP1rN9PM","message":{"content":[{"type":"text","text":"plum-2"}],"stop_reason":"end_turn","usage":{"input_tokens":2600,"cache_read_input_tokens":4231,"cache_creation_input_tokens":1088,"output_tokens":6,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"iterations":[{"input_tokens":2600,"output_tokens":6,"cache_read_input_tokens":4231,"cache_creation_input_tokens":1088,"type":"message"}]}}}


(Usage payloads abridged to the relevant fields; values are from a real run.)

Steps to Reproduce

mkdir /tmp/subagent-usage-repro && cd /tmp/subagent-usage-repro
claude -p --dangerously-skip-permissions 'Using the Agent tool, spawn 10 subagents IN PARALLEL (all in one message). Subagent N (1..10) gets exactly this prompt: "Reply with exactly the word plum-N. Do not use any tools." When all return, reply with the single word: done'

python3 - <<'EOF'
import json, glob, os, collections
for f in glob.glob(os.path.expanduser(
        "~/.claude/projects/-tmp-subagent-usage-repro/*/subagents/agent-*.jsonl")):
    reqs = collections.defaultdict(lambda: {"max": 0, "final": False})
    for line in open(f):
        r = json.loads(line)
        if r.get("type") != "assistant": continue
        m = r["message"]; u = m.get("usage") or {}
        e = reqs[r.get("requestId")]
        e["max"] = max(e["max"], u.get("output_tokens") or 0)
        e["final"] |= m.get("stop_reason") is not None
    for rid, e in reqs.items():
        print(os.path.basename(f), rid, "max_output_tokens:", e["max"],
              "ok" if e["final"] else "FINAL-USAGE-NEVER-WRITTEN")
EOF

Typical output: 8 agents show max_output_tokens: 6 ok, 2 show
max_output_tokens: 1 FINAL-USAGE-NEVER-WRITTEN. Every agent in the run
completed normally (all ten plum-N replies returned to the parent). Rates
are similar with sequential spawning, tool-using agents, and workflow
subagents; which agents are affected varies run to run.

Claude Model

Other

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

2.1.222

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

  • Environment: Claude Code 2.1.222, linux x64 native build

(Debian 13, kernel 6.12.x), default model (Fable 5). Seen from both the VS
Code extension and headless claude -p.

  • Related smaller observation: thinking blocks in subagent transcripts are

written with empty thinking text even on requests whose final usage shows
hundreds of thinking tokens (main-session files retain thinking text). If
intentional, fine — but it also removes any fallback for estimating lost
usage from content.

  • A killed/interrupted subagent (TaskStop) is NOT the trigger: kill tests

produced clean transcripts, and losses occur in agents that complete
normally and deliver their results.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗