[BUG] claude -p exits 0 ("success") while agent loop is mid-task — last assistant message has stop_reason tool_use, tool results delivered, no continuation API call
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 headless mode (claude --print --permission-mode bypassPermissions --output-format json "<task prompt>"), the agent loop intermittently terminates immediately after Bash tool results are returned, while the model is mid-task:
- The transcript's last assistant message has
stop_reason: "tool_use"— the model issued tool call(s) and expected to continue. - The tool(s) executed successfully and their
tool_resultrecords were appended to the transcript. - No further API call happens. The transcript simply ends on the
user/tool_resultentry. - The CLI exits 0 and emits a well-formed result JSON (the
usageblock parses normally), so automation reading exit code + result JSON sees a successful completion.
The session had done little or none of its assigned work at that point — one instance died 7 seconds in, 2 turns total, ~200 ms after two parallel Bash tool results returned.
Observed 5+ times across 2026-07-04 and 2026-07-05 on version 2.1.200, across two unrelated repos, dispatched by a local automation harness (each occurrence is an independent claude -p invocation). Re-running the identical prompt sometimes dies the same way again (two tasks failed identically on a retry hours later), while sibling invocations in the same batch complete normally.
Signatures of four confirmed instances (from session transcripts, entrypoint: sdk-cli):
| Session | Duration | Last assistant act | Dying tool call(s) | Result |
|---|---|---|---|---|
| A1 | 7 s, 2 turns | thinking + text + 2 parallel Bash tool_use, stop_reason tool_use | two grep -n … \| head reads | both results appended, loop ends, exit 0 |
| A2 | 26 s | Bash tool_use, stop_reason tool_use | python3 -m pytest … \| tail -30 | result appended ("No module named pytest"), loop ends, exit 0 |
| B1 (retry of a task that died the same way 5 h earlier) | ~90 s | Bash tool_use, stop_reason tool_use | go test ./internal/... -v | result appended, loop ends, exit 0 |
| B2 (retry, same pattern) | ~60 s | Bash tool_use, stop_reason tool_use | go test ./internal/handlers/ … | result appended, loop ends, exit 0 |
Transcript tail of instance A1 (paraphrased structure, timestamps verbatim):
21:36:12.734 assistant thinking stop_reason=tool_use
21:36:13.606 assistant text stop_reason=tool_use
21:36:14.497 assistant tool_use (Bash #1) stop_reason=tool_use
21:36:14.859 user tool_result #1
21:36:15.154 assistant tool_use (Bash #2) stop_reason=tool_use (same turn/usage)
21:36:15.347 user tool_result #2
— transcript ends; CLI exits 0 —
Ruled out:
- Timeout — harness limits were 900–1500 s; deaths at 7–90 s.
- Rate limit / quota — no banner in captured stdout+stderr; exit 0; usage block present.
- PreToolUse hook block — the dying tool calls executed and returned ordinary results (a blocked call would not have produced a tool_result); occurrences also continued after the relevant hook rule was disabled.
- Model refusing / stopping — stop_reason is
tool_use, notend_turn; the last text messages show the model actively mid-plan ("Let me run the tests first…").
What Should Happen?
After tool results are delivered, the loop should make the continuation API call and keep going until the model returns end_turn (or a real limit is hit). If the loop aborts for an internal reason, the result JSON should say so (subtype: "error_during_execution" or similar) with a non-zero exit code — never subtype: "success" + exit 0 with a pending tool_use on the transcript.
Error Messages/Logs
### Error Messages/Logs
None. That is the core of the report: no error surfaces anywhere — stdout result JSON is well-formed, exit code is 0, the transcript just stops. (The result JSONs were not retained by our automation at the time; on next occurrence we will capture `subtype`, `is_error`, `num_turns` — see Additional Information.)
Steps to Reproduce
No deterministic repro; intermittent under this shape:
- Dispatch a multi-step coding task headlessly:
claude --print --permission-mode bypassPermissions --output-format json "<prompt telling it to read a plan, edit files, run tests, commit>"in a repo working directory. - PreToolUse/PostToolUse hooks are configured in
~/.claude/settings.json(see Additional Information — possibly relevant, unconfirmed). - Most invocations complete normally; the affected ones end silently right after a Bash tool_result (observed after both parallel and sequential tool calls; 3 of 4 dying calls were test/build commands —
pytest,go test,go build— but one was two plaingrepreads).
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
unknown
Claude Code Version
2.1.200
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
- Family resemblance to known SDK issues, but distinct: claude-agent-sdk-typescript #339 (iterator ends without
resultevent after subagent + end_turn) and #333 (iterator hangs after final tool_result, never yields next assistant turn) share the "loop stops after tool results" pathology. This report differs: CLI entrypoint (not the SDKquery()API), top-level agent loop (no subagents involved), exit 0 with a well-formed result JSON, tools executed. Also distinct from claude-code #63583 (stop_reasontool_usewith missing tool_use block — here the blocks exist and ran) and #73372 (subagent truncation reported as completed — here it's the top-level loop). - Hook configuration present (macOS, so #71022's Windows hooks-inert-in-
-pissue shouldn't apply): PostToolUse hooks include a test-output witness script that matches test-runner commands; 3 of 4 dying tool calls were test/build commands. Possibly coincidental (the 4th was plain greps) — flagging in case PostToolUse output handling interacts with loop continuation in--printmode. - Impact: in automation, this is a silent false success — orchestrators trusting exit code + result JSON record the task complete when little or no work was done. We now treat exit-0-with-no-commit as failure as a workaround.
- On next occurrence we will capture and can supply: full result JSON (
subtype,is_error,num_turns,message),--output-format stream-json --verbose --include-hook-eventscapture, and sanitized transcript JSONL.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗