claude -p exits with stop_reason: tool_use and empty result mid-generation (stream interruption treated as success)
Description
When running claude -p in non-interactive/headless mode, the CLI can exit cleanly (subtype: "success", is_error: false, exit code 0) with stop_reason: "tool_use" and an empty result: "", even though the model was mid-generation and had not completed its task.
This is problematic for automation because the caller has no way to distinguish "agent finished successfully with no output" from "agent was interrupted mid-work."
Reproduction
Command:
claude --dangerously-skip-permissions --model sonnet -p --output-format stream-json --verbose "<prompt>"
Version: 2.1.85
Environment: GitHub Actions runner (Ubuntu 24.04), running as part of a CI automation pipeline.
Context: The agent was executing a multi-step task (analyzing a PR, generating tests, editing files). After 28 turns and 251 seconds, the agent had successfully generated test files and was starting a new sub-task (UI testing) when the session ended.
Observed Behavior
The final NDJSON result event:
{
"type": "result",
"subtype": "success",
"is_error": false,
"duration_ms": 251016,
"num_turns": 28,
"result": "",
"stop_reason": "tool_use",
"total_cost_usd": 0.895
}
The last assistant message before the result event was a thinking block with only 8 output tokens and stop_reason: null (streaming partial):
{
"type": "assistant",
"message": {
"content": [
{
"type": "thinking",
"thinking": "Let me try to navigate to the frontend app URL (http://localhost:5173) to attempt the UI test."
}
],
"stop_reason": null,
"usage": { "output_tokens": 8 }
}
}
The model was clearly mid-generation (emitted a thinking block but never produced the subsequent tool_use block). The API stream appears to have been interrupted, and Claude Code treated this as a successful completion.
Expected Behavior
One of the following:
- Retry the API call when a streaming response ends without a terminal
stop_reason(end_turn,max_tokens, orstop_sequence) - Report this as an error (e.g.,
subtype: "error_stream_interrupted") so callers can distinguish it from a genuine success - At minimum, do not report
subtype: "success"whenresultis empty andstop_reasonis"tool_use"— this combination means the agent wanted to continue but was stopped
What This Is NOT
Ruled out through analysis of the NDJSON log (84 entries, 28 turns):
| Hypothesis | Evidence Against |
|---|---|
| --max-turns limit | Not passed; would produce subtype: "error_max_turns" |
| --max-budget-usd | Not set; would produce subtype: "error_max_budget_usd" |
| External timeout (SIGTERM) | Result event is present and complete; process exited cleanly |
| Context window exhaustion | ~80k / 200k tokens used (40%) |
| Output token limit | 14.7k / 32k total; only 8 tokens in last turn |
| Permission denial | permission_denials: [] |
Impact
In our CI automation, result: "" + subtype: "success" causes the pipeline to treat the run as "no tests generated" and post a skip comment to the PR — even though the agent had already generated test files and made 4 edits. The $0.90 of API spend is wasted, and the PR author sees incorrect "No Tests Generated" output.
Additional Context
- The agent made 27 successful tool calls before this happened (MCP tools, file reads, edits, glob searches)
- The last successful tool was a
ToolSearchthat returned a tool reference — the model received it and started thinking about what to do with it - No errors, warnings, or anomalies appear anywhere in the 84-line NDJSON log
- The
duration_api_ms(250281) is very close toduration_ms(251016), suggesting no significant non-API time
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗