claude-code - 💡(How to fix) Fix [BUG] Agentic loop terminates after clean tool_result on empty-text/empty-thinking tool_use turn — Windows v2.1.138, long post-tool inference [2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#58756Fetched 2026-05-14 03:40:17
View on GitHub
Comments
2
Participants
2
Timeline
6
Reactions
0
Author
Timeline (top)
labeled ×4commented ×2

Error Message

On Windows Claude Code v2.1.138 (Opus 4.7, 1M context), the agentic loop occasionally terminates immediately after a clean tool_result is delivered back to the model, even though the preceding assistant message had stop_reason: "tool_use". The harness emits a stop_hook_summary with preventedContinuation: false, returns to the input prompt, and requires the user to manually type "continue" to resume. No error, no retry, no API failure record.

Error Messages/Logs

No error messages are surfaced to the user. The defining property of this bug is that everything looks clean: no isApiErrorMessage record, no stderr, no failed-tool indicator. The JSONL sequence for each event looks like this:

Root Cause

A striking property is turn_duration on stopped turns. The 4 in-session reproductions had turn_duration of 76,752 / 84,446 / 109,600 / 217,978 ms for tiny tool calls (small file Reads, or a cargo Bash call). Normal completed turns in the same session run 2–8 seconds end-to-end. So roughly the entire turn duration is spent after the tool_result is delivered to the model, waiting for the post-tool-result inference call to return. The agentic loop ends with no further assistant message, suggesting the SSE stream for that inference call was aborted silently (consistent with the SDK-level root cause documented in #38905) and the loop interpreted the absent response as a clean turn end.

Code Example

No error messages are surfaced to the user. The defining property of this bug is that everything looks clean: no `isApiErrorMessage` record, no stderr, no failed-tool indicator. The JSONL sequence for each event looks like this:


[assistant] stop_reason=tool_use, content=[{type:tool_use, name:Read, input:{...}}], textLen=0, thinkingLen=0
[user]      content=[{type:tool_result, tool_use_id:..., content:"..."}], interrupted=false
[system]    subtype=stop_hook_summary, preventedContinuation=false, hookErrors=[], level=suggestion, hasOutput=false
[system]    subtype=turn_duration, durationMs=217978, messageCount=833
[user]      content="continue"          ← manually typed by user OR replaced by an external schedule wakeup


Concrete instance (entry indices in `bb126c72-f8d3-4d50-9367-8df3ec07d018.jsonl`):


[802] assistant: stop_reason=tool_use, tools=[Read("edit_line_text.rs", offset=1, limit=15)], textLen=0, thinkingLen=0
[803] user:      tool_result, len=777 bytes, interrupted=false
[804] system:    stop_hook_summary, preventedContinuation=false, hookErrors=[], level=suggestion
[805] system:    turn_duration, durationMs=217978, messageCount=833
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

On Windows Claude Code v2.1.138 (Opus 4.7, 1M context), the agentic loop occasionally terminates immediately after a clean tool_result is delivered back to the model, even though the preceding assistant message had stop_reason: "tool_use". The harness emits a stop_hook_summary with preventedContinuation: false, returns to the input prompt, and requires the user to manually type "continue" to resume. No error, no retry, no API failure record.

This has happened across every implementation phase of a long project. I instrumented and analyzed it across 23 prior session JSONLs in this project, then deliberately reproduced it 4 times in a single session. The signature is identical every time:

On the assistant message immediately preceding the stop_hook_summary:

  • message.stop_reason: "tool_use"
  • exactly 1 tool_use block (never parallel — 0/28 stops happened on a parallel-tool turn)
  • content[].text total length = 0
  • content[].thinking total length = 0

On the stop_hook_summary entry that follows:

  • preventedContinuation: false
  • hookErrors: []
  • level: "suggestion"
  • hasOutput: false

This rules out user-side Stop hooks as the cause — the agentic loop terminates before the Stop hook runs.

A striking property is turn_duration on stopped turns. The 4 in-session reproductions had turn_duration of 76,752 / 84,446 / 109,600 / 217,978 ms for tiny tool calls (small file Reads, or a cargo Bash call). Normal completed turns in the same session run 2–8 seconds end-to-end. So roughly the entire turn duration is spent after the tool_result is delivered to the model, waiting for the post-tool-result inference call to return. The agentic loop ends with no further assistant message, suggesting the SSE stream for that inference call was aborted silently (consistent with the SDK-level root cause documented in #38905) and the loop interpreted the absent response as a clean turn end.

The rate is also bursty. Baseline across 23 prior sessions: 33 stops in 6,011 tool_use turns ≈ 0.55%. During the high-volume repro phase in this session: ~7.7% (2 stops in ~26 silent single-tool Reads). Suggests a per-session factor (cache state, server-side latency band, or context-size weighting) that shifts the rate from very-rare to frequent.

What Should Happen?

When stop_reason: "tool_use" was indicated on the prior turn and the tool_result is delivered cleanly, Claude Code should re-invoke the model with the tool_result. If the post-tool-result SSE stream aborts, the agent loop should detect the abort (missing message_stop event, stop_reason: null, or zero content blocks) and either retry the inference call or surface a visible "stream aborted, retrying…" message — not fire Stop hooks as if the turn completed normally.

Error Messages/Logs

No error messages are surfaced to the user. The defining property of this bug is that everything looks clean: no `isApiErrorMessage` record, no stderr, no failed-tool indicator. The JSONL sequence for each event looks like this:


[assistant] stop_reason=tool_use, content=[{type:tool_use, name:Read, input:{...}}], textLen=0, thinkingLen=0
[user]      content=[{type:tool_result, tool_use_id:..., content:"..."}], interrupted=false
[system]    subtype=stop_hook_summary, preventedContinuation=false, hookErrors=[], level=suggestion, hasOutput=false
[system]    subtype=turn_duration, durationMs=217978, messageCount=833
[user]      content="continue"          ← manually typed by user OR replaced by an external schedule wakeup


Concrete instance (entry indices in `bb126c72-f8d3-4d50-9367-8df3ec07d018.jsonl`):


[802] assistant: stop_reason=tool_use, tools=[Read("edit_line_text.rs", offset=1, limit=15)], textLen=0, thinkingLen=0
[803] user:      tool_result, len=777 bytes, interrupted=false
[804] system:    stop_hook_summary, preventedContinuation=false, hookErrors=[], level=suggestion
[805] system:    turn_duration, durationMs=217978, messageCount=833

Steps to Reproduce

The bug is stochastic at a low baseline rate (~0.5%) and bursty (~7%+ once a session hits it). To reproduce reliably you need volume.

  1. Start a fresh Claude Code session on Windows with Opus 4.7 (1M context).
  2. Ask Claude to read many small file ranges sequentially as single-tool turns (one Read per assistant message, no parallel tools), e.g. "silently read 30 different small file ranges in this repo, one Read per message, no commentary, no thinking." This deliberately produces assistant turns matching the signature: 1 tool_use, textLen=0, thinkingLen=0.
  3. Wait. Most calls complete in 2–8 seconds end-to-end. Occasionally one will hang for 60+ seconds (in my data: 77s, 84s, 110s, 218s) and then the agentic loop terminates instead of producing the next assistant message.
  4. The terminal returns to the input prompt. The JSONL shows stop_hook_summary with preventedContinuation: false right after the tool_result, then turn_duration with the elapsed milliseconds, then the cursor is awaiting user input.
  5. User types literally anything ("continue", "go", etc.) and Claude resumes from the next logical step.

If you'd like a deterministic harness, I built one using /loop dynamic mode with a 180-second safety-net ScheduleWakeup. It armed a recovery wakeup before each batch of silent reads; when the bug fired mid-batch, the safety net rescued the loop on the next wake-up, and a scanner identified the new bug event from the JSONL without any user input. 2 out of 2 in-loop firings were auto-recovered this way — I can share the harness on request.

Session id with 4 clean reproductions: bb126c72-f8d3-4d50-9367-8df3ec07d018. JSONL path: ~/.claude/projects/D--PROJECTS-continuity/bb126c72-f8d3-4d50-9367-8df3ec07d018.jsonl. I can attach an excerpt showing all 4 events on request.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

No response

Claude Code Version

2.1.140 (Claude Code)

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Windows Terminal

Additional Information

Universal fingerprint (28/28 events: 4 this session + 24 historical across 23 sessions)

Every observed bug event shares the four-property assistant-turn signature listed in What's Wrong: stop_reason: tool_use, exactly 1 tool_use, textLen=0, thinkingLen=0. Tool mix: Read 21, Bash 10, MCP tool 1, Read 3 (in this session). The common factor is the assistant-turn shape, not the tool.

What this is NOT (eliminated from instrumentation)

  • Not the user's Stop hook. preventedContinuation: false, hookErrors: []. Hook runs after the agentic loop has already ended.
  • Not tool errors. interrupted: false, no isError on any tool_result.
  • Not parallel tools. 0/28 stopped turns had >1 tool_use block. Parallel-tool turns are immune in our data.
  • Not long sessions only. This session's messageCount at stop ranged from 132 → 845. Earlier "long session" correlation was a sampling artifact.
  • Not MCP plugin-induced. 0/28 stops happened on an MCP tool call in this session's high-volume harness; the only one was incidental in the 23-session historical baseline.

Relationship to existing issues

Same bug family as several existing issues, but a distinct variant:

  • #38905 (closed as duplicate) — silent SSE stream abort. Root-cause analysis there identifies the SDK's SSE iterator silently swallowing AbortError / FetchRequestCanceledException. That mechanism likely applies, but #38905 describes an abort on the initial inference stream; mine is on the post-tool-result inference call (consistent with textLen=0 AND thinkingLen=0 — the model never gets to start emitting any content blocks).
  • #47931 (Windows, open, stale, has-repro) — same platform, also silent terminate after tool_result, but in that case the Node process actually dies and the JSONL ends with no stop_hook_summary or turn_duration. Mine produces clean stop_hook_summary + turn_duration records — the harness keeps running, only the agentic loop ends.
  • #40462 (macOS+vscode, open, stale) — "send any message to unstick" matches my "user types continue", but their UI shows Thinking... indefinitely; mine ends the loop cleanly with a prompt.
  • #29881 (Linux, closed not-planned) — silent tool stop with Stop hook NOT fired. Mine fires the hook with preventedContinuation: false, so that variant's fix wouldn't catch mine.
  • #13973 — "No response requested." Mine emits empty content, not the literal "No response requested." string.

Suggested fix direction

In the agent loop, after consuming the SSE stream for the post-tool-result inference call:

  1. If the prior turn had stop_reason: "tool_use" AND the new assistant message has 0 content blocks (or only thinking with no text/tool_use), treat it as an aborted stream and retry the inference call.
  2. At minimum, surface a visible "post-tool-result stream aborted, retrying…" message instead of silently ending the loop and firing Stop hooks.

Artifacts available on request

  • stop_log.json — chronological 4-event log with full tool inputs and per-event turn_duration_ms.
  • Excerpt of bb126c72-...jsonl showing the exact [assistant tool_use] → [user tool_result] → [system stop_hook_summary preventedContinuation:false] → [system turn_duration] sequence for each of the 4 events.
  • The /loop repro harness (deterministic scheduling + safety-net ScheduleWakeup + JSONL stop-detector) that auto-recovered 2/2 in-loop firings.

stop_log.json bug-evidence-excerpt.json

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] Agentic loop terminates after clean tool_result on empty-text/empty-thinking tool_use turn — Windows v2.1.138, long post-tool inference [2 comments, 2 participants]