[BUG] Subagent replies split by the output-token ceiling: only the last assistant message reaches the caller, the rest is dropped silently
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?
When a subagent's final reply exceeds the output-token ceiling, the model stops withstop_reason: "max_tokens" and continues in a second assistant message. Only that
second message is delivered to the caller. Everything generated before the split is
discarded — with no error, no warning, and no truncation marker anywhere in the
delivered result.
The caller has no way to tell a complete answer from a 16%-of-the-answer fragment. It
reads as a normal, successful agent result.
Both delivery paths are affected:
- Synchronous agents → the
tool_resultcontains only the final message. - Background agents → the
<result>block of thetask-notificationcontains only
the final message, while <status> still says completed.
Real-world impact in my case: a subagent produced a 151,652-character extraction across
two messages (64,000 + 11,770 output tokens). The caller received 24,353
characters. 127,215 characters — 84% of the work — were dropped, and the loss was
only discovered weeks later by reading the raw transcript. The content was still in the
subagent transcript the whole time; it simply never crossed to the caller.
This is distinct from the known "output runs away to max_tokens" reports
(issues #76048, #77033 and #78516), which are about producing too many tokens.
This report is about what happens to the content afterwards.
What Should Happen?
The caller should receive the whole answer: all consecutive assistant messages that form
one logical reply — i.e. every message whose predecessor stopped withstop_reason: "max_tokens" — concatenated, not just the final one.
Failing that, the delivered result should at minimum carry an explicit truncation
marker so the caller knows content is missing. Claude Code already does exactly this for
API-error partial recovery, prefixing recovered history with:
Everything below is PARTIAL output recovered from the agent before it was cut off. The agent did NOT finish its task — treat these results as incomplete.
A max_tokens split gets no such marker, so silent data loss is the default outcome.
Error Messages/Logs
There is no error output — that is the core of the report. The evidence is the mismatch
between the subagent transcript and what the caller received.
Subagent transcript (2 assistant messages, one logical answer):
stop_reason=max_tokens output_tokens=2000 len=3703 "1\n2\n3\n...\n953"
stop_reason=end_turn output_tokens= 778 len=1188 "954\n955\n...\n1200"
What the parent received in the Agent tool_result:
[{"type": "text", "text": "954\n955\n956\n957\n958\n..."}]
^^^ starts at 954 -- the first 3,703 characters are gone
Background-agent variant, same repro with run_in_background: true
task-notification <status>completed</status>, <result> length 1,152 of 4,891 chars
For comparison, the same failure at the default ceiling on claude-opus-5:
subagent msg 1: stop_reason=max_tokens output_tokens=64,000 127,215 chars
subagent msg 2: stop_reason=end_turn output_tokens=11,770 24,329 chars
delivered <result> in the task-notification: 24,353 chars
lost: 127,215 chars (83.9%)
Steps to Reproduce
Lowering CLAUDE_CODE_MAX_OUTPUT_TOKENS is only to make the repro fast and cheap — the
identical failure occurs at the default ceiling whenever a reply is long enough to hit
it. The ceiling is passed via --settings rather than the shell, because a value for
this variable in the user's settings.json takes precedence over the same variable in
the process environment; passing it in the shell silently has no effect for anyone who
has it configured.
- Create an empty working directory and a settings file that lowers the ceiling:
```bash
mkdir -p /tmp/mt-repro && cd /tmp/mt-repro
cat > repro-settings.json <<'JSON'
{
"env": {
"CLAUDE_CODE_MAX_OUTPUT_TOKENS": "2000",
"CLAUDE_CODE_SUBAGENT_MODEL": "claude-haiku-4-5"
}
}
JSON
```
- Run a single headless session that delegates to one subagent:
``bash``
claude -p 'Call the Agent tool exactly once with subagent_type "general-purpose", description "long output test", and this prompt: "Print the integers from 1 to 1200, one per line, with no other text, no preamble and no summary. Your final message must contain all 1200 lines." After the agent returns, reply with only: FIRST=<first line of the result> LAST=<last line of the result>' \
--settings ./repro-settings.json --allowedTools Agent --max-turns 8
- Expected:
FIRST=1 LAST=1200
Actual: FIRST=949 LAST=1200 (the exact split point varies per run)
- Confirm the subagent actually produced all 1200 lines — the content is present in its
transcript, in two messages:
``bash``
python3 - <<'PY'
import json, glob, os
for f in glob.glob(os.path.expanduser(
'~/.claude/projects/-tmp-mt-repro/*/subagents/*.jsonl')):
for line in open(f, errors='replace'):
rec = json.loads(line)
if rec.get('type') != 'assistant':
continue
msg = rec.get('message') or {}
blocks = msg.get('content')
if not isinstance(blocks, list):
continue
text = ''.join(b.get('text', '') for b in blocks
if isinstance(b, dict) and b.get('type') == 'text')
if text:
print(f"stop={msg.get('stop_reason'):<11} len={len(text):>6} "
f"head={text[:16]!r} tail={text[-16:]!r}")
PY
- For the background-agent path, add
run_in_background: trueto the Agent call in
step 2. The task-notification reports <status>completed</status> and its
<result> likewise contains only the final message.
Claude Code Version
2.1.220
Operating System
Ubuntu/Debian Linux (WSL2)
Terminal/Shell
WSL (Windows Subsystem for Linux)
Additional Information
Observation that may help localise this. The delivered result is *byte-for-byte
identical* to the subagent's final assistant message — 1,208 characters in the repro
above, an exact match with no prefix, suffix, or separator. Whatever assembles an
agent's result appears to take that single message rather than the run of messages that
together form the reply, and this holds on both the synchronous and the background
path.
Related issues.
- Issue #74427 — Tool results fabricated at output-token-limit resume boundary. Same
resume boundary as this report, different symptom: that one fabricates content across
the boundary, this one drops it. Possibly the same code path.
- Issue #68882 — *Silent empty response when
stop_reason: max_tokenswith extended
thinking and no text block.* Adjacent: another silent failure at the same boundary.
- Issue #78460 — *Subagents are capped at 8000 output tokens;
CLAUDE_CODE_MAX_OUTPUT_TOKENS
does not apply.* This no longer reproduces on 2.1.220: setting
CLAUDE_CODE_MAX_OUTPUT_TOKENS=2000 caps a subagent at exactly 2,000 output tokens,
and an unconstrained subagent on this version emitted a single 64,000-token message.
Mentioning it because the two reports would otherwise look contradictory.
Workaround. Raising CLAUDE_CODE_MAX_OUTPUT_TOKENS toward the model's ceiling
(128,000 on claude-opus-5) reduces how often the split happens but does not fix the
loss when it does. It also appears to be free: the context reservation derived from this
value is capped well below the default, so raising it does not shrink the usable input
window.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗