[BUG] Headless run (claude -p) discards the entire session when the final turn exceeds CLAUDE_CODE_MAX_OUTPUT_TOKENS — no partial output, no checkpoint, no resume
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?
Summary
In non-interactive / headless mode (claude -p ... --output-format json), if the model's final assistant turn exceeds the output-token maximum (default 32000), the run aborts with an API error and the entire session is lost: no files written, no partial transcript persisted, and no --resume-able artifact. A long, expensive run can therefore produce nothing despite completing most of the work.
In my case a 36-minute, $3.00, 12-turn derivation task was lost in full. The agent had (apparently) done the work but batched its results + a file Write into one oversized final message; that message was rejected wholesale, so the Write tool call inside it never executed. Net output: zero
Environment
Claude Code via claude -p (headless), launched with nohup, --output-format json, --permission-mode acceptEdits, --max-budget-usd 4.00.
macOS; invoked through a wrapper script.
(Second occurrence of the same class: the prior headless run completed only because it happened to keep its final message small.)
What Should Happen?
Expected
Intermediate state (completed tool calls, written files, prior assistant turns) is persisted as it happens, so a cap-exceeded error on the final turn does not erase earlier work.
The error is recoverable — e.g. a resumable session ID / saved transcript, or the partial output flushed to the --output-format sink.
Ideally, the model is steered to write files incrementally rather than batching everything into one terminal message that can be rejected atomically.
Actual
The run terminates with is_error: true and the entire transcript is discarded. No files on disk, no partial JSON, no resume handle. All compute time and cost is forfeited.
Error Messages/Logs
{
"is_error": true,
"subtype": "success",
"total_cost_usd": 2.9987043,
"duration_ms": 2166900, // ~36 min
"num_turns": 12,
"result": "API Error: Claude's response exceeded the 32000 output token maximum. To configure this behavior, set the CLAUDE_CODE_MAX_OUTPUT_TOKENS environment variable."
}
Steps to Reproduce
Steps to reproduce
Run a claude -p "<long task that produces a large final answer + a file write>" job in headless mode with --output-format json.
Let the model accumulate substantial work and emit it (including a tool call such as Write) in a single large final assistant turn.
The final turn exceeds the 32000 output-token cap.
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
Version 1.13576.4 (414f85)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
Impact
Total loss of long-running, paid work with no recovery path. The longer/more valuable the run, the larger the loss — the failure mode scales the wrong way.
The error message points to CLAUDE_CODE_MAX_OUTPUT_TOKENS, but raising the cap only moves the cliff; it does not address the lack of checkpointing/persistence, which is the real defect.
Suggested fixes (any of these would prevent total loss)
Persist incrementally. Flush each completed event (assistant turns, tool calls, tool results, file writes) to the --output-format sink / a session log as they occur, not only at the end. Then a final-turn error leaves a usable partial record.
Make headless runs resumable. Emit a session ID / checkpoint that --resume (or equivalent) can pick up after an error.
Graceful degradation on cap-exceeded. Instead of discarding the whole turn, return the truncated content + a structured max_output_tokens_exceeded signal so the caller can recover/continue.
Tool-call safety. A pending file Write should not be silently dropped because the surrounding message was over the cap; consider executing committed tool calls before enforcing the message-size limit, or surfacing them separately.
Workaround (for others hitting this)
Run with --output-format stream-json --verbose so every event is captured to disk in real time, and instruct the agent to write output files incrementally (and to write a stub file as its first action). This makes a cap-exceeded abort recoverable, but it is scaffolding the harness should provide natively.