Headless stream-json crashes with 'Cannot read properties of undefined (reading input_tokens)' on default model (Opus 4.7)
[Bug] Headless mode crashes with "Cannot read properties of undefined (reading 'input_tokens')" on default model (Opus 4.7)
Summary
Running claude -p --output-format stream-json --input-format stream-json in headless mode crashes immediately with:
Cannot read properties of undefined (reading 'input_tokens')
The crash reproduces on a fresh workdir with no --resume and no prior session file — so it is not a corrupted-session issue. The same exact command line with --model claude-opus-4-6 added completes successfully, which strongly suggests the default model (Opus 4.7 in my case) produces a response shape that the usage parser doesn't handle.
Environment
- Claude Code:
2.1.112(latest stable, installed via winget) - OS: Windows 11 Enterprise 10.0.26200
- Shell: Git Bash (MSYS2)
- Default model: Opus 4.7 (1M context)
- Wrapper: Multica local agent daemon spawning
claudeas subprocess. Same issue occurs with manually-invokedclaude -pin the same mode.
Reproduction
Headless stream-json invocation without --model:
claude -p --output-format stream-json --input-format stream-json \
--verbose --strict-mcp-config --permission-mode bypassPermissions
Fails within ~7 seconds with the input_tokens error. The synthetic error message gets persisted to ~/.claude/projects/<proj>/<sid>.jsonl as an assistant message with model "<synthetic>" and isApiErrorMessage: true, meaning subsequent --resume attempts read the corrupted history and either fail the same way or propagate the error.
Comparison — failing vs working commands
All four commands below ran against the same daemon, same workdir root, within a 35-minute window. The only meaningful difference is the presence of --model claude-opus-4-6.
❌ Fails (uses default model = Opus 4.7)
claude -p --output-format stream-json --input-format stream-json \
--verbose --strict-mcp-config --permission-mode bypassPermissions
claude -p --output-format stream-json --input-format stream-json \
--verbose --strict-mcp-config --permission-mode bypassPermissions \
--resume <sid>
Both fail at ~3–10s with Cannot read properties of undefined (reading 'input_tokens').
✅ Works (explicit --model claude-opus-4-6)
claude -p --output-format stream-json --input-format stream-json \
--verbose --strict-mcp-config --permission-mode bypassPermissions \
--model claude-opus-4-6
claude -p --output-format stream-json --input-format stream-json \
--verbose --strict-mcp-config --permission-mode bypassPermissions \
--resume <sid> --model claude-opus-4-6
Runs to completion, tool calls work, session resumes correctly.
Corrupted session artefact
After a crash, the session JSONL contains an entry like this (verbatim, trimmed):
{
"type": "assistant",
"message": {
"id": "bdccefb0-0407-4e11-bd59-42674cd0a59b",
"model": "<synthetic>",
"role": "assistant",
"stop_reason": "stop_sequence",
"usage": {
"input_tokens": 0,
"output_tokens": 0,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0
},
"content": [
{ "type": "text", "text": "Cannot read properties of undefined (reading 'input_tokens')" }
]
},
"isApiErrorMessage": true,
"version": "2.1.112"
}
This makes the failure sticky: once one task crashes, every subsequent --resume of that session appears to re-trigger the same path (8–10s, 0 tools).
Likely root cause
The usage accumulator in stream-json output likely reads response.usage.input_tokens without a null check. With Opus 4.7 default model, some early streamed message (possibly an empty/reasoning-only chunk, tool-use delta, or cache init response) has usage === undefined, triggering the TypeError.
Two independent issues seem to be in play:
- Defensive-coding miss:
usageshould be guarded — a missing field shouldn't abort the run. - Model-specific trigger: Opus 4.7 + headless stream-json hits this path consistently; Opus 4.6 doesn't.
Impact
- All headless agent wrappers (Multica, OpenHands-style, custom CI bots) that don't hard-code
--modelcannot use the CLI's default model. - The error is persisted to session history, poisoning
--resumeand confusing users into thinking their session file is corrupted (I spent ~1 hour debugging session files before realizing the issue was model selection).
Workaround
Add --model claude-opus-4-6 (or any Sonnet variant) explicitly to every headless invocation. This should be documented until the usage-parse bug is fixed.
Suggested fix
- Guard
response?.usage?.input_tokens ?? 0at the usage-accumulation site. - Don't persist
<synthetic>error messages to the session JSONL, or mark them so--resumeskips them. - Add a regression test for
--output-format stream-jsonwith each supported model.
Logs
Daemon log excerpts (timestamps from 2026-04-17):
15:58:55 starting agent workdir=...\53360a05\workdir model="" reused=false
15:58:55 agent command exec=claude args="[-p --output-format stream-json ... --permission-mode bypassPermissions]"
15:59:02 agent text="Cannot read properties of undefined (reading 'input_tokens')"
15:59:04 claude finished status=failed duration=9.764s
16:28:29 starting agent workdir=...\0523067c\workdir model="" reused=false
16:28:29 agent command exec=claude args="[-p ... --permission-mode bypassPermissions --model claude-opus-4-6]"
16:28:39 tool #1: Bash
... (runs to completion)
Happy to provide full stack traces or the poisoned JSONL if that helps.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗