[BUG] Claude Code hangs indefinitely when API streaming connection stalls (no read timeout)

Open 💬 34 comments Opened Feb 15, 2026 by esuleman

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?

Claude Code hangs permanently when the API streaming response stalls mid-delivery. The process stays alive (epoll_wait in kernel) but makes no progress. The UI shows a spinner ("Accomplishing…",
"Ruminating…", etc.) indefinitely. No error is surfaced. The session cannot recover — the only fix is kill -9.

Two distinct hang patterns observed:

Pattern 1 — Mid-turn stream freeze: The API response starts streaming (thinking block received, "thought for 3s" renders), then the stream silently stops. No more tokens arrive. The JSONL session
log shows no new entries after the last progress update. The process is stuck waiting for bytes that never come.

Pattern 2 — Tool result delivery stall: A Bash tool executes and completes (e.g., curl --max-time 5), but the tool result is never delivered back to the conversation state. The last tool_use ID in
the JSONL has no matching tool_result. The UI shows the spinner as if the tool is still running.

Both patterns correlate with background agent (Task with run_in_background: true) notifications arriving between or during turns.

What Should Happen?

  1. Claude Code should have a read timeout on the SSE/streaming HTTP response. If no data arrives within N seconds (e.g., 60–120s), it should abort the request and surface a retryable error to the

user.

  1. Tool result delivery should have a timeout. If a tool completes but the result isn't consumed within N seconds, the session should error rather than hang.
  2. The UI should detect "no progress for N seconds" and offer the user an escape (e.g., "Session appears stuck. Press Enter to retry or Esc to cancel").

Error Messages/Logs

Session 1: Mid-turn stream freeze (fff87be2)

  JSONL timeline:
  21:43:28 UTC  Turn 1 ends (system/turn_duration logged)
  21:43:28      Background agent notification injected as user message
                → Triggers Turn 2
  21:44:44      Turn 2 ends (system/turn_duration logged, stop hooks run)
                Background agent notification for a DIFFERENT agent arrives
                → Triggers Turn 3
                API call starts, "thought for 3s" renders in UI
                *** No more JSONL entries. Stream froze. ***
                UI stuck on "Accomplishing… (thought for 3s)" for 10+ minutes

  Process state during hang:
  $ ps -p 843072 -o pid,state,wchan
      PID S WCHAN
   843072 S do_epoll_wait

  $ kill -TERM 843072   # no effect
  $ kill -KILL 843072   # required to terminate

  Session 2: Tool result delivery stall (06d50a72)

  JSONL shows:
  Last tool_use ID:    toolu_vrtx_01JqgVUPZLrMyudEQaAQhx1x  (Bash: curl)
  Last tool_result ID: toolu_vrtx_01LJscxroh9R7tsDQUL6AhuF  (different, earlier tool)
  → Mismatch: the curl's result was never delivered

  The curl command had --max-time 5 so it completed, but the result never made it back to the conversation. UI showed "Ruminating…" indefinitely.

Steps to Reproduce

Difficult to reproduce deterministically — it appears to be a race condition or network-level issue. But the following pattern triggers it frequently:

  1. Start Claude Code on a remote Linux server via SSH + tmux
  2. Give it a complex task that spawns multiple background agents (Task with run_in_background: true)
  3. Wait for background agents to complete and deliver notifications
  4. When a notification arrives right as a turn is ending or between turns, the next API call has a high chance of hanging

Environment factors that may contribute:

  • Remote server (high-latency network path to API)
  • tmux (terminal multiplexing)
  • Multiple concurrent Claude Code sessions in different tmux panes
  • Heavy hook infrastructure (6 hook events, though all complete in <120ms per telemetry)

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

2.1.40

Claude Code Version

2.1.42

Platform

Google Vertex AI

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

Suggested fix

Add a read timeout to the HTTP streaming client. Pseudocode:

// In the SSE/streaming response handler:
const STREAM_READ_TIMEOUT_MS = 120_000; // 2 minutes

let lastDataTime = Date.now();
stream.on('data', (chunk) => {
lastDataTime = Date.now();
// ... process chunk
});

const watchdog = setInterval(() => {
if (Date.now() - lastDataTime > STREAM_READ_TIMEOUT_MS) {
stream.destroy(new Error('API stream read timeout'));
clearInterval(watchdog);
}
}, 10_000);

Workaround

External watchdog daemon that monitors JSONL session files and kills processes with no writes for >5 minutes. Available at user's request.

View original on GitHub ↗

34 Comments

github-actions[bot] · 5 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/24688
  2. https://github.com/anthropics/claude-code/issues/20572
  3. https://github.com/anthropics/claude-code/issues/25442

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

reski-rukmantiyo · 5 months ago

I also experience the same. how to downgrade it?

fmallet · 4 months ago

Experiencing the same issue — all models affected, not just Opus 4.6

Environment:

  • macOS, Claude Code via CLI and Claude Desktop (Code tab)
  • Subscription: Max plan
  • Auth method: claude.ai (first-party)
  • Claude Code version: 2.1.62

Symptoms:

  • Every prompt (even trivial ones like "are you there?") hangs indefinitely on spinner messages (shlepping, channeling, pondering…)
  • Token count does not increase — no work is happening server-side
  • Sending a follow-up message in the same stuck session results in "Failed to load session"
  • Started suddenly overnight (Feb 26–27, 2026), no local changes made

What I've verified:

| Check | Result |
|-------|--------|
| curl -s https://api.anthropic.com/v1/messages -o /dev/null -w "%{http_code}" | 405 — API is reachable |
| claude auth status | Logged in, valid credentials |
| Deleted ~/.claude/projects/ and ~/.claude/sessions/ | No effect |
| Tested with Opus | Hangs |
| Tested with Sonnet | Hangs too |
| Tested via CLI | Hangs |
| Tested via Claude Desktop Code tab | Hangs |

Key observation:

This is not limited to Opus 4.6. Sonnet is equally affected, suggesting the streaming/SSE stall issue may be broader than initially reported.

jurmadani · 4 months ago

I agree with you @fmallet I have the same problem since Monday, until yesterday I got it to kinda work by interrupting him and say "continue" but now it does not work at all.

fmallet · 4 months ago

Update: Resolved — corrupted ~/.claude directory was the root cause

Following up on my previous comment. After extensive debugging, I found the fix.

What solved it

mv ~/.claude ~/.claude.bak
claude "are you there?"
# → Works immediately

Removing the ~/.claude directory and letting Claude Code recreate it from scratch resolved the issue completely.

What did NOT help

| Attempted fix | Result |
|---------------|--------|
| Deleting ~/.claude/projects/ and ~/.claude/sessions/ | No effect |
| Updating Claude Code (2.1.45 → 2.1.62) | No effect |
| Launching from a clean directory (/tmp/test-claude) | Still hung |
| Testing with Sonnet instead of Opus | Still hung |

Root cause hypothesis

The issue appears to be a corrupted state in ~/.claude/.claude.json, likely in the cachedGrowthBookFeatures (feature flags cache) or related cached data. Claude Code was failing during early initialization — before even reaching the API call or producing any debug output.

Key observation

The hang affected all models (Opus and Sonnet), both CLI and Claude Desktop Code tab, but Chat and Cowork tabs in Claude Desktop worked fine (they don't go through the Claude Code client).

Suggestion for the Claude Code team

  • A claude reset command would be very helpful for this type of situation
  • The client should not hang silently when initialization fails — at minimum a timeout + error message

Hope this helps others experiencing the same issue.

jurmadani · 4 months ago

@fmallet You are a legend, tried your solution and it worked. I tried to debug this the whole week and could not understand what is the problem.

jurmadani · 4 months ago

it worked for a while then it stopped working again lol

ChrisEdwards · 4 months ago

Corroborating data from Bedrock user — 12-hour log analysis

I filed #29344 independently and was pointed here as a duplicate. My data corroborates everything in this issue, from the Bedrock side (vs your Vertex findings). Adding my analysis here since it has concrete numbers.

Environment

  • Claude Code 2.1.62
  • Bedrock (CLAUDE_CODE_USE_BEDROCK=1)
  • Model: us.anthropic.claude-opus-4-6-v1
  • maxOutputTokens: 64000
  • macOS Darwin 24.6.0

Aggregate Stats (12-hour window, evening through next afternoon)

| Metric | Value |
|--------|-------|
| Sessions active in period | 24 |
| Incomplete responses (stop_reason: null with partial content) | 395 |
| Error entries logged for those failures | 0 |
| Stream stalls >= 3 minutes | 18 |
| Stream stalls >= 5 minutes | 14 |
| Max single stall duration | 599 seconds (~10 min) |

Zero errors were surfaced to the UI or logged to the session JSONL for 395 incomplete API responses.

Both patterns confirmed on Bedrock

Pattern 1 — Mid-turn stream freeze (same as OP's Pattern 1):

  • Assistant began streaming thinking token → 552s stall → eventually resumed via system heartbeat
  • Assistant streamed 89 chars of text → 599s stall → resumed via system heartbeat
  • Assistant streamed 172 chars of text → 459s stall → user manually interrupted
  • Assistant issued a tool_use:Read → 284s stall → system heartbeat broke it

Pattern 2 — Tool result delivery stall (same as OP's Pattern 2):

  • tool_result delivered → 592s wait → assistant finally responded
  • tool_result delivered → 577s wait → system heartbeat
  • tool_result delivered → 554s wait → system heartbeat

The 300-second heartbeat loop

Many sessions show repeating system → system entries at exactly 300-second intervals, sometimes 10-15+ in a row. The worst case showed 25 consecutive 300s heartbeats spanning over 2 hours while the session appeared stuck to the user. The spinner runs the whole time with no indication anything is wrong.

Impact

  • Sessions running subagents or multi-step tasks silently stall and never recover without manual intervention
  • Long-running overnight sessions accumulate stalls that waste hours
  • Users have no way to distinguish "slow generation" from "dead stream" — the spinner is identical
  • Only recovery is Escape/Ctrl+C and re-prompting, losing the current generation

Key takeaway

This is not Vertex-specific — it reproduces identically on Bedrock. The core issue is that Claude Code has no client-side stream idle timeout and silently swallows incomplete responses without logging errors or surfacing them to the user.

ChrisEdwards · 4 months ago

FYI, I deleted my claude.json file, but that did not help.

ALSO FYI: This succeeded when run in OpenCode with the same model and thinking. So this is not a Bedrock issue.

mloiterman · 4 months ago

This basically renders claude useless for me. Having to interrupt and type continue is only partially effective and it seems like it risks leaving things in an unknown state.

I've tried recreating all .claude directories and .json files, but no change. Happens for me in remote sessions and locally. In tmux and outside of tmux.

What additional information can be provided to accelerate this?

Using Version: 2.1.69 on a Mac locally and in Debian 12.10 remotely.

kolkov · 4 months ago

We reverse-engineered cli.js across 12 npm versions and analyzed 1,571 sessions (148,444 tool calls, 8,007 orphaned) to find the root causes of this and related hang/orphan issues.

Full analysis with code offsets and fix proposals: #33949

blakeley · 3 months ago

I'm still encountering this

jackstine · 3 months ago

i think it has something to do with reading a large file...

Here is the session with the agent, it says the agent is thinking, but there is no thinking event in the session.

and this entire time it is eating tokens. the entire time, it was reading this file for 22 minutes now

This occurs on both models opus and sonnet

a10aa665-717e-4272-87aa-0bdb5c8c1d52.txt

<img width="1417" height="223" alt="Image" src="https://github.com/user-attachments/assets/f2a66109-27e3-475b-95af-1be9ad5f37b5" />

jackstine · 3 months ago

splitting the file into multiple files, originally 1K lines into 5 files worked, but it still stalled for 5 minutes on first file.

emmahyde · 3 months ago

This has been happening constantly this weekend.

001005HS · 3 months ago

Still reproducing on 2026-04-13 — 2+ minute Metamorphosing stall before the first server call

Adding a fresh data point since this is still happening on current Claude Code.

Environment

  • OS: Linux 6.19.6-1-t2-noble
  • Claude Code CLI, Opus 4.6 (1M context)
  • Plan: paid

Symptom (from the top status line during a brainstorming prompt)

Metamorphosing… (2m 7s · ↓ 40 tokens · thought for 109s)

The session sat in Metamorphosing for 2 minutes 7 seconds with only 40 tokens downloaded and 109 s of reported "thought" before the first server call actually went out. Only 40 tokens landed during the entire 2+ minute window, and the spinner kept running the whole time. No error, no timeout, no indication to the user that anything was wrong — identical to the patterns described earlier in this thread.

What the user actually experiences

  • Submit a multi-step creative prompt (brainstorm user-abuse scenarios for an app).
  • Status line shows Metamorphosing… immediately.
  • Seconds tick up, token counter frozen near 0.
  • 2+ minutes pass with no visible progress.
  • Eventually a server call fires and work starts.
  • From the user's point of view this is indistinguishable from a hang, and on longer stalls the user has already Ctrl+C'd by the time the stream would have resumed.

Why I'm commenting here instead of a new issue

  • Searched existing issues, #25979 is the open tracking issue for this exact symptom.
  • #26157 and a few others were auto-closed as duplicates of this one.
  • Reports from 2026-04-11 / 2026-04-12 in this thread describe the same "spinner runs while nothing actually happens" pattern, and the kolkov cli.js analysis (#33949) proposes fixes at specific code offsets — this is still unresolved as of 2026-04-13.

What would help end users right now, in rough order of impact:

  1. Client-side stream-idle timeout with visible countdown — if no tokens arrive for N seconds, surface "stream stalled, retrying…" in the UI instead of the same spinner.
  2. Automatic reconnect on stalled SSE, not just 300 s heartbeat silent loops.
  3. Distinct status for "waiting on server" vs "receiving" — the current Metamorphosing label collapses both states, which is why users cannot tell a slow generation from a dead stream.
  4. claude reset (or an in-session /reset) that nukes ~/.claude/.claude.json cache so users don't have to mv ~/.claude ~/.claude.bak by hand.

Happy to pull session logs if a specific format helps the triage team.

aliforia-com · 3 months ago

+1
The same problem occurred several times.

tigertop-c · 3 months ago

Same issue

yankarinRG · 3 months ago

Same issue and 73% of my weekly token wasted, thanks...

oscar-o-oneill · 3 months ago

I keep getting "API Error: Stream idle timeout - partial response received" in Claude Code on the web. I think it might be related to CC processing large files? Anyway, when I run the same prompt in Claude Code CLI it works fine. Hopefully Anthropic can figure it out and fix Claude Code on the web, because it's so much easier to work there.

mrayhanulmasud · 2 months ago

I fixed it by first running /compact in one command, then asking “please complete the code” in the next command—and it worked.

oscar-o-oneill · 2 months ago

@mrayhanulmasud, cool, thanks. I might try that out if it keeps happening. Another possible workaround is to just log out of Claude and log back in.

The other thing that actually worked perfectly was just using CC CLI teleport, so you can continue a web based Claude Code session in the CLI, and surprisingly enough, it works!

Just run claude --teleport in your terminal and open the CC web session.

Wiktor-P · 2 months ago

+1 — from the Remote Control side. Framed by Claude.

Scenario: session hangs mid-tool-call on the host (Windows 11, Opus 4.7).
Wall clock advances, token counter frozen. I'm away from the host, connected
via mobile Remote Control. The only recovery path is physically walking back
to the host to press ESC — which defeats the point of Remote Control.

Adding a concrete UX ask on top of the programmatic interrupt already
requested here:

### Remote Control "Stop" button

A visible cancel control in the Remote Control web & mobile clients,
mirroring the local ESC UX — active while a turn is in progress.

  • Sends the same interrupt signal as local ESC (whatever that ends up being

once the underlying issue is fixed: SIGINT-equivalent, cancel token,
whichever).

  • Should work even when the host is in the "frozen token counter" state

(#44921), not only during healthy tool calls — otherwise it won't help
the actual hang scenarios.

  • Input queue behavior: typing a new message from remote currently only

queues it behind the hung turn. A Stop button would clear the queue
or at least gate sending on successful interrupt.

### Why UI matters alongside the API

The programmatic interrupt asked for above covers SDK/CI users. The mobile
user isn't going to shell in from a phone to send a signal — they need a
button. Shipping only the API leaves Remote Control users without a
recovery path.

Related:

  • #44921 (frozen token counter — the hang pattern this would recover from)
  • #17466 (local ESC also fails during active tool calls — interrupt

mechanism itself needs work)

  • #32457 (Remote Control button-state bug — different issue but shows the

Remote UI already has a Send/Stop state machine to build on)

CaptFaraday · 2 months ago

Found a fix for the API Error: Stream idle timeout - partial response received error. Adding one line to a config file resolves it for me. Full details below.

The fix

Open ~/.claude/settings.json and add this to the "env" section (create the section if it doesn't exist):

"env": {
  "CLAUDE_STREAM_IDLE_TIMEOUT_MS": "1800000"
}

Then quit and restart Claude Code. That number is 30 minutes in milliseconds, replacing a hidden 90-second default that the client uses before assuming the connection has gone idle.

When I was hitting it

  • Claude Code v2.1.119 on Windows
  • Opus 4.7 (1M context), xhigh effort, thinking enabled
  • Asked Claude to write a long spec or plan in one shot
  • Claude would say something like "Writing the spec now" and then go quiet for ~90 seconds before the error popped up

What didn't work

  • Turning off remote-control (remoteControlAtStartup: false) — same failure on local CLI
  • Asking Claude to break the work into smaller chunks — didn't help reliably

How I tracked it down

The timing. Opened the session log and measured every timeout. They all fired between 90.0 and 91.7 seconds after the previous message — eight in a row. That's not random — it's a 90-second clock running out.

The binary. The claude.exe binary references CLAUDE_STREAM_IDLE_TIMEOUT_MS in two places. The one my stream hits defaults to 90 seconds with no minimum, so raising the env var directly raises the ceiling. The variable is real and load-bearing — it's just not in the official env-vars reference, so nobody knew it was the lever.

Confirmation it actually works

After setting the env var to 1,800,000 and restarting:

  • A 737-line spec write that had failed twice in a row went through on the first try
  • A follow-up 3,518-line plan write also wrote cleanly
  • Same model, same context size, same prompt — only the env var changed

Suggestion for Anthropic

This variable is real and important but undocumented — please either add it to the env-vars reference page or raise the default. Opus 4.7 with thinking + large context easily goes silent for over 90 seconds while preparing a big tool call, and most users have no way to know what's wrong or how to fix it.

Cross-ref: #25979 (main tracking issue), #33949 (technical deep-dive).

datus1982 · 2 months ago

See #54434 for a fresh repro with detailed mid-stream stall timing data; I added an OpenClaw-side independent confirmation here: https://github.com/anthropics/claude-code/issues/54434#issuecomment-4339679618

Same root cause as the 'no read timeout' pattern (your Pattern 1 — mid-turn stream freeze) documented in this issue, observed in a different invocation path (claude-cli spawned as a subprocess with -p --output-format stream-json --verbose). Patch suggested in #25979's "What Should Happen" #1 (per-chunk read timeout on SSE) is exactly the right shape.

danieledagnelli · 2 months ago

Same issue when running in a remote visual studio code and i close my laptop. Historically it would have continued in the background.

eggrollofchaos · 1 month ago

Still recurring as of CLI 2.1.158, and seemingly server-side as well — see #54434 (stream stalls without message_stop) and #63880 (delayed-batch tool-result delivery). A client-side read timeout like the one proposed here would at least bound the hang instead of leaving it open-ended.

tdodd777 · 1 month ago

Adding a corroborating occurrence on a newer version than most reports in this thread.

Environment: Claude Code v2.1.167 (native install), Sonnet 4.6, Ubuntu 24.04, detached tmux, long-running session (~4h, heavy tool use).

Behavior observed: mid-turn, immediately after a large tool_result was returned (an API search response of ~178 items), the session parked permanently:

  • CPU dropped to idle and stayed there; no error was ever rendered, and the last pane output was a normal in-progress line
  • Input went dead: text sent to the pane via tmux send-keys was accepted by the terminal but never consumed by the TUI (for 31 minutes, until intervention), matching the "queue events accumulate but are never dequeued" behavior in #53328
  • The only recovery was killing and recreating the session; a fresh session connected and worked instantly afterward

We investigated the host side before attributing this to the client: no network events, OOM, memory pressure, or DNS issues coincided with the hang, and other traffic from the same machine flowed normally through the window. Consistent with a silently stalled stream and no client-side read timeout.

If it recurs we should have per-turn client-side data to share (transcript persistence and an external liveness monitor are now in place).

serge-disruptive · 1 month ago

Adding a data point and an OS-level mitigation that aren't in the thread yet.

Still present well past the "2.1.40 regression" window — and correlated with a macOS upgrade rather than a Claude Code version. Hitting Pattern 1 (mid-turn stream freeze) on Claude Code 2.1.168 (native build), macOS 26.5.1 (25F80), in a session over SSH + tmux with background subagents — i.e. the conditions described here. The relevant detail: it started the moment the machine was upgraded to macOS 26 and rebooted; it was not happening on the prior OS. During the hang the process sits at 0% CPU with the HTTP-client thread parked and no new transcript writes — blocked on a socket that's open but dead, exactly as reported.

That timing suggests this is less a Claude Code version regression and more an environmental trigger: macOS 26's network stack appears to drop idle long-lived connections without a clean FIN, and the missing stream read-timeout turns each such drop into an indefinite hang.

A mitigation that works without an app-side change (Pattern 1 only): macOS ships with net.inet.tcp.always_keepalive=0 and keepidle=7200000 (2h), so the kernel never probes a half-open socket. Claude Code already retries dropped connections (CLAUDE_CODE_MAX_RETRIES) but not a silently stalled stream — so enabling kernel keepalive converts the undetectable stall into a detectable drop, which the existing retry path then recovers:

sudo sysctl -w net.inet.tcp.always_keepalive=1 \
                net.inet.tcp.keepidle=120000 \
                net.inet.tcp.keepintvl=15000 \
                net.inet.tcp.keepcnt=4

With these, a dead connection is detected in ~3 min instead of hanging forever. Caveat: this is grounded in the retry-on-dropped-connection behavior and our process analysis; it targets Pattern 1 (the dead-socket stream freeze) and will not help Pattern 2 (the tool-result delivery race), which isn't a socket-level stall. A client-side SSE read-timeout, as suggested above, remains the proper fix.

serge-disruptive · 1 month ago

OS-level mitigation: aggressive TCP keepalive turns the infinite hang into a ~3-min auto-recovery

Hit Pattern 1 repeatedly on a Mac mini (Vertex, via SSH + tmux). Confirmed the process sits in do_epoll_wait at 0% CPU — blocked on a socket read with no timeout, matching the OP.

A contributing factor on macOS specifically: the default TCP keepalive is effectively off for this purpose —

net.inet.tcp.always_keepalive = 0
net.inet.tcp.keepidle = 7200000   # 2 hours

So when the streaming socket dies mid-response, the kernel never probes it and the client waits forever. A client-side stream read timeout (as requested above) is still the right fix, but until then, making the kernel detect dead sockets is an effective workaround:

sudo sysctl -w net.inet.tcp.always_keepalive=1
sudo sysctl -w net.inet.tcp.keepidle=120000     # 2 min
sudo sysctl -w net.inet.tcp.keepintvl=15000
sudo sysctl -w net.inet.tcp.keepcnt=4

The kernel now declares a dead connection in ~3 min; the socket errors, and Claude Code's existing retry (CLAUDE_CODE_MAX_RETRIES) recovers — infinite hang → ~3-min blip, no kill -9. Kernel-wide, so it's app-independent. To make it survive reboots, re-apply the sysctls at boot via a LaunchDaemon.

This doesn't help a stall where the socket is still alive but no bytes flow (that genuinely needs the client read timeout), but it eliminates the dead-socket class of this bug.

dudupstmaxx · 1 month ago

Reproducing this on Windows + direct Anthropic API (not just Vertex/macOS) — adding frequency data and netstat forensics.

We run several long-lived Claude Code sessions as Windows services (NSSM, headless, Telegram plugin attached). We hit exactly this hang, and our data suggests it's not specific to Vertex AI, macOS, or tmux — we see it on Windows talking to the direct Anthropic API.

Symptom (matches the report): claude.exe stays alive but makes zero progress — no new tokens, the turn never completes. Process must be killed to recover.

Forensics we captured on frozen processes:

  • CPU delta over a sampling window = 0 (user and system), while threads/handles stay allocated → the process is blocked, not looping.
  • netstat on the frozen PID shows the connection to the Anthropic API endpoint (...:443) still held — ESTABLISHED, and in some captures CLOSE_WAIT (server sent FIN, client never processed it). Consistent with a streaming read blocked indefinitely with no read timeout — exactly the root cause described here.
  • The plugin/host process is healthy; only the model turn is stuck.

Frequency / severity (single day, 4 instances):

  • 26 freeze→recovery events in ~24h; 22 matched this "soft-hang" pattern.
  • Repeated clusters of 3–5 freezes within ~15 min on the same instance (freeze → kill → fresh start → freeze again). One instance accounted for 54% of events.
  • Several freezes happened right after the user sent a new instruction — the message was silently swallowed by the already-stalled turn.

Our workaround (we independently arrived at the same approach suggested here):
An external watchdog that detects staleness via the session's stdout/JSONL not advancing, then kills + restarts. Empirically our detector fires at a median of ~3.8 min of staleness. It works, but it's a blunt instrument: it kills the whole process and loses in-flight context, and a kill-storm can hit local restart caps.

Why a client-side read timeout would fix this cleanly: a configurable read/idle timeout on the SSE stream (the 2-minute threshold suggested above is reasonable) that surfaces a normal retryable error would let the client recover the turn in place, instead of every integrator building a process-killing watchdog. A keepalive/heartbeat on the stream would be an even better signal.

Happy to share minidumps of the frozen claude.exe (we capture procdump at freeze time) if the stack traces would help pinpoint where the read blocks. Likely related: #33949.

Environment: Claude Code (native Windows binary), Windows 10, direct Anthropic API (api.anthropic.com), multiple concurrent headless sessions.

sgupge2663 · 1 month ago

Reproducing on native Windows 11 + first-party Anthropic API (not Vertex/Foundry), Opus 4.8 / CLI 2.1.170 — same tool_result-then-silence stall. Confirming the Vertex/Foundry-only idle-timeout fix doesn't cover first-party here.

dudupstmaxx · 1 month ago

Follow-up from the same Windows + first-party Anthropic API setup I posted from on Jun 11 — it's getting worse, and the recent idle-timeout fix doesn't seem to reach first-party.

  • Frequency is climbing. Same 4 long-lived headless instances (Windows services, direct api.anthropic.com): 37 soft-hang freeze→recovery events in a single day (2026-06-15), up from the 26/day I reported on Jun 11. Identical signature each time — claude.exe alive at 0% CPU, the socket to api.anthropic.com:443 still held (ESTABLISHED / CLOSE_WAIT), zero new transcript writes, the turn never completes.
  • Corroborating @sgupge2663 (Jun 13): we're also on native Windows + first-party Anthropic API (not Vertex/Foundry), Opus 4.8. The Vertex/Foundry idle-timeout fix does not cover us — first-party still has no client-side stream read timeout, so the hang remains unbounded here.
  • The missing timeout compounds at scale. Since the only recovery is an external kill+restart, high-volume days now exhaust our restart rate-caps; once the cap trips, recovered sessions can no longer --resume and come back as fresh sessions with the in-flight context lost. The blast radius of "no read timeout" isn't one stalled turn — at volume it becomes repeated context loss across long-lived sessions.
  • The macOS sysctl keepalive mitigation has no working equivalent for us on Windows: we tested the KeepAliveTime registry tuning and disabling IPv6, and neither resolved it — the socket stays ESTABLISHED (not dropped), so kernel keepalive never converts it into a retryable error.

Ask: please extend the client-side SSE read/idle timeout already shipped for Vertex/Foundry to the first-party Anthropic API path as well. That one change would let the existing retry recover the turn in place and remove the need for every integrator to run a process-killing watchdog. Minidumps of the frozen claude.exe available on request.

sgupge2663 · 1 month ago

@dudupstmaxx thanks for the corroboration — same setup here (native Windows 11, first-party API, Opus 4.8). Adding the application-side counterpart to your netstat/CPU forensics, plus the first request_id in this thread.

Caught one in a transcript (CLI 2.1.161): a tool_result returned at 04:42:37Z, then ~12 min of nothing until the Stop hook fired at 04:54:49Z. Log-side detail: no follow-up generation request is ever issued — there's no request_id for the model turn that should have consumed the tool_result; it never starts. So the block may sit a step earlier than yours: your socket is held after the next request goes out (ESTABLISHED/CLOSE_WAIT, response never arrives); here it stalls before the next request is even emitted. Either way, same unbounded wait with no read/idle timeout on first-party to break it.

Reads like serge-disruptive's Pattern 2 (tool_result-side stall, not the dead-socket Pattern 1) — a first-party instance, now with a request_id to correlate:

  • request_id that produced the tool_result (last record before the gap): req_011CbqBPEY38MSQwXwaCFc7G

+1 on extending the Vertex/Foundry idle-timeout to the first-party path — it would let the existing retry recover these in place instead of forcing a process kill.