[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

Open 💬 1 comment Opened Jul 6, 2026 by cubicleDowns

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_result records were appended to the transcript.
  • No further API call happens. The transcript simply ends on the user/tool_result entry.
  • The CLI exits 0 and emits a well-formed result JSON (the usage block 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, not end_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:

  1. 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.
  2. PreToolUse/PostToolUse hooks are configured in ~/.claude/settings.json (see Additional Information — possibly relevant, unconfirmed).
  3. 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 plain grep reads).

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 result event 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 SDK query() 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_reason tool_use with 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--p issue 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 --print mode.
  • 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-events capture, and sanitized transcript JSONL.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗