[Bug] SSE stream stalls in long-running sessions without message_stop event
SSE stream from /v1/messages stalls mid-response main-thread requests (no message_stop, no error event)
Summary
In long-running interactive Claude Code sessions, SSE responses from /v1/messages repeatedly stop emitting events mid-response without sending message_stop or any error event. The connection stays open and the client correctly waits — but no further bytes arrive. Most stalls recover on their own after ~50–70s; some never recover and only resolve when the user presses ESC. Subagent streams in the same session are unaffected.
Claude Code's own [Stall] telemetry (stream_idle_partial, Streaming stall detected, Streaming completed with N stall(s)) cleanly captures the behavior — this issue is filing what that telemetry is reporting.
Environment
- Claude Code version:
2.1.119(native install,cc_version=2.1.119.9c7, entrypointcli) - Model:
claude-opus-4-7andclaude-sonnet-4-6 - Subscription: Team,
rateLimitTier=default_claude_max_5x - OS: Linux 7.0.0-1-cachyos, terminal Konsole 26.04.0
- Relevant env:
CLAUDE_STREAM_IDLE_TIMEOUT_MS=900000API_TIMEOUT_MS=600000CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1CLAUDE_CODE_DISABLE_1M_CONTEXT=1
Reproduction
Not deterministic but very high frequency: in a single 26-minute working session (b3ad7516-35cd-47cb-9e98-6916ee362882), 5 distinct stalls occurred — roughly one every five minutes of active use. Behavior reproduces across sessions.
The stalls happen during normal multi-turn coding work (tool calls + bash + edits). No special conditions required.
Observed behavior (from debug log)
All five stalls were on source=repl_main_thread, each with a different x-client-request-id. All stalled early in the response (under ~5.4KB / ~1000 tokens streamed), and then went silent with the connection still open.
| # | Stall start (UTC) | x-client-request-id | Bytes when frozen | Outcome |
|---|---|---|---|---|
| 1 | 2026-04-26T19:25:46Z | 721d0de1-200f-422e-b7c8-c90dd211f62c | 737 | Recovered after 51.8s |
| 2 | 2026-04-26T19:28:45Z | 2f671a02-f62f-433a-8cae-6ac3afdb1832 | 4336 | Recovered after 67.6s |
| 3a | 2026-04-26T19:40:43Z | 2f39599a-9ce9-4d65-ba2d-6476635db3b4 | 659 | Recovered after ~16s |
| 3b | 2026-04-26T19:41:17Z | 307a8268-8e8a-4afd-ab90-8ac8efc4c722 | 653 | Recovered after ~8s |
| 4 | 2026-04-26T19:45:57Z | 28380ec8-29ba-4e2e-8818-f4a2af125182 | 5373 | Did not recover (≥120s of zero bytes when log was captured; user had to ESC) |
The 51.8s and 67.6s recovery times look like they line up with a 60s server-side internal timeout/retry boundary.
Representative log excerpt (stall # 4, the one that didn't recover)
2026-04-26T19:45:26.603Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=28380ec8-29ba-4e2e-8818-f4a2af125182 source=repl_main_thread
2026-04-26T19:45:28.731Z [DEBUG] Stream started - received first chunk
2026-04-26T19:45:42.866Z [DEBUG] (last activity from this stream)
2026-04-26T19:45:57.864Z [WARN] [Stall] stream_idle_partial lastChunkAgeMs=15000 bytesTotal=5373 idleDeadlineMs=900000
2026-04-26T19:46:12.863Z [WARN] [Stall] stream_idle_partial lastChunkAgeMs=30000 bytesTotal=5373 idleDeadlineMs=900000
2026-04-26T19:46:42.864Z [WARN] [Stall] stream_idle_partial lastChunkAgeMs=60001 bytesTotal=5373 idleDeadlineMs=900000
2026-04-26T19:47:42.864Z [WARN] [Stall] stream_idle_partial lastChunkAgeMs=120000 bytesTotal=5373 idleDeadlineMs=900000
bytesTotal=5373 is unchanged across all four warnings — i.e. zero bytes received during the 2-minute window. No message_stop, no error event, no TCP-level disconnect.
Representative log excerpt (stall # 1, which recovered)
2026-04-26T19:25:27.805Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=721d0de1-200f-422e-b7c8-c90dd211f62c source=repl_main_thread
2026-04-26T19:25:30.445Z [DEBUG] Stream started - received first chunk
2026-04-26T19:25:46.135Z [WARN] [Stall] stream_idle_partial lastChunkAgeMs=15001 bytesTotal=737 idleDeadlineMs=900000
2026-04-26T19:26:13.288Z [WARN] [Stall] stream_idle_partial lastChunkAgeMs=14999 bytesTotal=773 idleDeadlineMs=900000
2026-04-26T19:26:22.912Z [WARN] Streaming stall detected: 51.8s gap between events (stall #1)
2026-04-26T19:26:43.288Z [WARN] [Stall] stream_idle_partial lastChunkAgeMs=15000 bytesTotal=17424 idleDeadlineMs=900000
2026-04-26T19:26:48.862Z [WARN] Streaming completed with 1 stall(s), total stall time: 51.8s
Note that bytesTotal only moves from 737 → 773 between the first two warnings (a trickle), then jumps to 17424 after the gap is over. That suggests the server held back roughly 16KB of generated tokens for ~50s before flushing.
What's not happening
- Not the client: the client is doing the right thing —
[Stall] stream_idle_partialfires every 15s while waiting, no read errors are logged. - Not the network: the connection stays open the whole time. TCP isn't dropping. No proxy involved (
ANTHROPIC_CUSTOM_HEADERS=false, noAuthorizationoverride). - Not the hooks/tools:
tool_dispatch_*events all complete normally and quickly. Hooks aren't wedged. No tool exceeds even a second. - Not the local environment: bash/LSP/MCP all healthy. The 6 unauthorized
claude.aiMCP connectors and the one Mermaid-Chart 502 at startup are unrelated and complete in milliseconds. - Not subagents: ~30+ requests with
source=agent:builtin:Exploreorsource=agent:builtin:Planin the same session, none stalled. Onlysource=repl_main_threadrequests are affected. - Not context-size: most recent stall fired with
autocompact: tokens=73852 threshold=167000— well under any threshold.
What I think is happening (speculation)
A server-side issue specific to streaming on the path used by main-thread Claude Code requests. The pattern (early-token stall, ~60s internal recovery boundary, never affects subagents) is consistent with an overloaded/restarting inference worker holding generated tokens before flushing them downstream — and occasionally dropping the rest.
Session metadata for cross-reference
- Session ID:
b3ad7516-35cd-47cb-9e98-6916ee362882 - Project: medium-sized git repo (~121 tracked files), no LSP servers active
- Plugins enabled:
feature-dev,code-review,ralph-loop,playwright(MCP),commit-commands,security-guidance,pr-review-toolkit,claude-code-setup,superpowers-extended-cc attribution header x-anthropic-billing-header: cc_version=2.1.119.9c7; cc_entrypoint=cli
Happy to share the full debug log directly with Anthropic engineering if useful.
Showing cached comments. Read the full discussion on GitHub ↗
14 Comments
Independent second confirmation on macOS arm64 + complementary OpenClaw-side observation.
Hitting the same SSE mid-stream stall pattern, identical claude version (
2.1.119), but on a different OS (macOS Tahoe, arm64). Different invocation path:claudeis being spawned as a subprocess by OpenClaw2026.4.24withclaude -p --output-format stream-json --verbose --permission-mode bypassPermissions ...rather than the interactive REPL.What I observe (concurs with #54434)
When the stall fires:
lsof -p <claude_pid>showsTCP api.anthropic.com:443 ... ESTABLISHEDwith zero bytes flowingsample <claude_pid>shows the main thread parked inkevent64(Bun event loop idle, waiting on I/O)message_stopevent, no error event — exactly as KingPin reportsI separately ruled out the Datadog telemetry endpoint (
http-intake.logs.us5.datadoghq.com) by/etc/hostsblackholing it and observing identical wedge behavior afterward —lsofpost-block confirmed only the Anthropic socket was ESTABLISHED. The hang is definitively on the Anthropic SSE stream, consistent with KingPin's repro.Complementary observation from the OpenClaw side
OpenClaw's gateway has a per-process watchdog (
noOutputTimeoutMs, default180000ms with a 180s floor) that kills theclaudesubprocess if no stdout arrives for the configured duration. So in our setup the stall doesn't surface to the user as a frozen UI — it surfaces asCLI produced no output for 180s and was terminated. (timeout)and aFailoverErrorto the next model. The user-visible symptom is "every model in the fallback chain returns 'all models failed', taking 13 minutes total" rather than KingPin's "spinner forever, ESC to recover."We patched the OpenClaw watchdog floor down to 60s to shrink the user-facing pain to ~3 minutes (full chain walk through 5 models × 60s each), at which point we were going to file an upstream issue here — but #54434 already nails the root cause and #25979 has the same symptom with a different invocation path. Adding our data instead of opening a duplicate.
Important caveat against aggressive client-side timeouts (relevant to anyone considering a workaround)
KingPin's data shows most stalls naturally recover at 50–70s (51.8s, 67.6s in his table). Aggressive client-side timeouts below ~75s will amputate naturally-recoverable stalls, not just terminate unrecoverable ones. We're seeing this trade-off in our 60s OpenClaw watchdog — some terminations are likely killing streams that would have self-resolved within another 10s. For the proper fix path: a per-chunk read timeout in claude's HTTP client at ~120–180s (well past the natural recovery window) would catch genuinely-stuck streams without false-positive termination of slow-but-recovering ones.
Suggested fix shape (concurs with #25979's "1.")
Add a per-chunk SSE read timeout to claude's HTTP client (Bun's
fetch/ SSE consumer doesn't impose one by default). On timeout, abort the stream, surface a retryable error with the partial response if any, and let the existing retry/backoff logic kick in. Default of 120–180s would clear the natural recovery window seen in #54434's data while still bounding the worst case.Happy to share the OpenClaw-side patch (
CLI_FRESH_WATCHDOG_DEFAULTS.minMs) and the wedge-sampler script we use to capture stall stacks if useful. Configuration:2.1.119cliBackend.args: ["-p", "--output-format", "stream-json", "--verbose", "--permission-mode", "bypassPermissions"]claude-opus-4-7,claude-sonnet-4-6,claude-opus-4-6,claude-opus-4-5,claude-sonnet-4-5,claude-haiku-4-5— all exhibit the same stall pattern, suggesting the issue is in the HTTP client, not model-specific.CLAUDE_STREAM_IDLE_TIMEOUT_MSnot set (using defaults)This is stream-completion integrity failure.
If SSE streams stall in long-running sessions without a
message_stopevent, then the product loses the event that tells operators and downstream logic whether a turn actually reached a completed state. That breaks more than UI polish — it breaks the contract around turn finality.Why it matters:
message_stopfor cleanup or state transitions can hang indefinitelyI would want clarity on:
message_stopis absentFor agent systems, end-of-turn certainty is part of control. If completion can become ambiguous, every downstream recovery decision gets harder.
this is getting really bad now, with hours of uselessness at a time. havent gotten anything out of Claude in past 3 hours so far. and this is happening for multiple hours every single day now. Intermittent small amounts of output with the majority of the tasks timing out with
API Error: Stream idle timeout - partial response receivedReproducing on Linux/Bun with the runtime's stall detector firing as documented.
Environment
--resumeCadence in a 7-minute window after resuming a long session:
5 stalls in 7 minutes, 53–65 s each, ~60-second cadence — matches your "5 stalls in 26 min, 50–70 s, every ~5 min" almost exactly.
Useful diagnostic technique: the
[DEBUG] Fast mode unavailablelog fires every ~1 second during normal operation. When the event loop is blocked, the heartbeat itself goes silent — that's a clean signal that the block is event-loop, not network. In our log, every long gap is preceded by the heartbeat as the last line. That suggests at least some of these stalls are client-side (event loop blocked), not purely server-side SSE issues.Possible upstream cause: at the time of these stalls the umbrella process had VmRSS 6.5 GB and a heap profile consistent with a major-GC working set that takes tens of seconds to mark-and-sweep. Restarting the session (starting from a fresh small heap) eliminates the stalls. Filed leak data in #56335.
This issue may be downstream of the heap retention. Worth checking if a fix to the underlying retention reduces stall frequency.
Adding a much-longer-duration data point that fits your "some never recover" branch — this stall went 7h 58m before manual intervention.
Environment
claude --resume <session-id>on macOS 26.xxhigh(set via/effort maxat session start)Timeline (from the session JSONL)
The hang occurred in a long-running interactive session (
ac79b4e9-71ea-4a37-a8ce-03bcff6632df, ~5,265 lines / 12 MB). Excerpting the boundary:The model had just produced a burst of 6 tool_use calls (5239–5245) — a sequence of grep/awk/Read calls investigating React-hook dependency arrays. The last tool_result came back with the ESLint output, and the model was supposed to take that turn to produce its next response. Nothing came back for 7h 58m. The session JSONL contains zero events in that window — no assistant text, no Sentry capture, no error event, no
[Stall]warning, no recovery, no retry. Just a flat gap.Why this is high-signal for #54434
CLAUDE_STREAM_IDLE_TIMEOUT_MSdefaults to 900s (15 min) per your environment. If that timer was active here, it should have fired ~32 times. Either the timer doesn't apply to this code path, or it isn't surfacing when it does fire on--resumesessions.--resumeof a long-running session. Samesource=repl_main_threadcode path you identified.Differences worth flagging
xhigheffort is in play here. Could be that the higher-effort path has a different stall behavior or different timeout discipline. Worth checking whether the affected code path differs between effort levels.[Stall]warnings logged. In your reproduction,[Stall] stream_idle_partialfired every 15s during the wait. In this session's JSONL, no such warning appears at all — but I'm reading from the JSONL, not the debug log, so it's possible the warnings exist there. Will retrieve and update if found.Cross-references
--resumestartup hangs (don't return to prompt), not mid-session stalls.If maintainers want the full session JSONL for diagnostic purposes, happy to share it directly via support — it's 12 MB and contains private repo paths so I won't attach here.
Bonus data point in the same thread: while I was drafting the previous comment, the desktop app (CCD) had its own ~2h SSE stall on the same workflow. So this isn't CLI-specific — same bug, same protocol, both client surfaces.
Environment
What was happening when it stalled
Investigating the previous CLI hang (this issue's repro). Sequence of recent activity right before the desktop stall:
gh issue listandgh searchshell commands to find related issues — all returned successfullygh issue view 54434 --json bodyandgh issue view 55241 --json bodyin a single Bash invocation — output streamed back fine, ~4 KB totalThat's where the SSE stream stalled. The desktop client sat at a "Thinking…" indicator for ~2h with no token output, no error, no recovery, no Sentry capture. User had to type a new message ("you just got stuck for 2 hours, wtf?") to interrupt and re-prompt — at which point the response came back immediately.
Why this matters for triage
The 8h CLI case from my previous comment + this 2h desktop case are same bug, same trigger pattern (mid-response stall after tool_result), two different client surfaces. So the fix can't live in either client alone — the silent-stream behavior is upstream of both, in the SSE pipe from
/v1/messages. That tracks with this issue's diagnosis (server-side worker holding tokens before flushing, occasionally dropping the rest).Practical UX consequences worth surfacing:
Main webview render process gone; this one fires nothing at all).In both cases, neither client surfaces the stall to the user despite Claude Code's own
[Stall] stream_idle_partialtelemetry detecting it server-internally. An ESC-equivalent prompt or "stream idle for >Ns, want to retry?" affordance would prevent the worst-case workflow loss even if the underlying server-side fix takes longer to land.Reproducing this on Windows 11 with the Claude Code VS Code extension. Same fingerprint as the original report, different platform, with multi-network confirmation that rules out the network layer entirely.
Environment
2.1.142.2af(VS Code extension,cc_entrypoint=claude-vscode)2.1.88(npm-global via nvm, Node 24.13.1) — same stall behavior in bothSymptom
Fresh session, zero files in context, single prompt. Stream starts normally, receives ~650 bytes, then goes silent.
[Stall] stream_idle_partialwarnings fire every 15 seconds. Connection stays open. No error surfaced.Key detail: the time-to-first-stall is mechanically consistent at 15.003 seconds across every test — same to the millisecond on three different networks.
Log excerpts (three separate sessions, three different networks)
Ethernet, NordVPN on (source IP
10.5.0.2):Ethernet, NordVPN off (source IP
192.168.0.88, direct via ISP):T-Mobile mobile hotspot, NordVPN off (IPv6 source, completely different ISP/path):
All three: fresh session,
tracking 0 files,autocompact: tokens=[REDACTED] level=ok, same prompt.What was ruled out
EnableNetworkProtection: 0)curl.exe -v -I https://api.anthropic.com/v1/messagesreturns 405 in ~200ms on every network tested. The API endpoint is reachable; only long-lived SSE streams stall.Additional observations
bytesTotalis consistently in the 630–666 range across all stalls — suggests the stream dies after roughly the same initial burst regardless of prompt or network.http/1.1(Windows schannel via curl) — unclear if the SDK negotiates differently.Stream started - received first chunkalways fires 2–3 seconds after request — initial RTT is fine.Client request IDs
For correlation:
9e7cc463-3c86-4f0b-8fa4-7fbbd9952acb29afebe7-4802-4f33-afb6-d70ec662776e6ae2e8cf-e386-4ac7-9697-92e3bc5a9c215d7e0230-a30e-4ffa-a070-b5a7d10af471212baedd-aee0-4121-90ab-48c97de23ed2Happy to provide full debug logs, additional traces, or test specific theories. The multi-network confirmation took some effort to set up but the result is unambiguous: this is not a client-side network issue.
Still seeing this, every request stalls 90s then retries successfully. Fresh session, no context bloat.
Request IDs from this morning (UTC):
7fd8ae54-022e-4262-ab98-b8d300d13f5b 13:04
989186af-9743-43de-9fe1-8be52a6e3a6d 13:06
cf1745c0-914f-4b48-94d7-f68ace4bf99e 13:21
Platform: macOS, VS Code extension, cc_entrypoint=claude-vscode, Max plan OAuth
Been days of this with any use of Opus 4.7 in the Claude VSCode plugin. Doesn't matter if it's a simple or complex task, things hang for huge amounts of time with every prompt. While they do eventually finish sometimes, Claude's output just shows an endless stream of this with the stall time compounding to be longer and longer each time:
Possibly related: #63880 reports the same delayed-delivery shape from the client side —
Bashtool results coming back as(Bash completed with no output)and then flushing in a later batch, which lines up with the "server holds tokens, then flushes late" mechanism described here.Grepping my local CLI transcripts for that empty-output signature turned up 222 occurrences across 43 sessions, spanning CLI 2.1.112 → 2.1.158 — so this isn't a recent regression. The cost when it isn't caught early is significant: the empty returns read as a stalled session and trigger retry round-trips (one of mine ran ~90 assistant turns over ~9.5 min before self-terminating). The client-side read-timeout/stall-detector asked for in #25979 would bound that. Can share transcript excerpts if useful.
Also hitting SSE stalls on long sessions — the 8-hour one in the comments really resonates. I wrote a watcher called claude-tmux-dog (https://github.com/SnowAIGirl/claude-tmux-dog) that monitors session liveness and token usage, and auto-recovers when the stream goes quiet. It is event-driven (Claude Stop hook triggers recovery flows), handles compaction, quota nudge scheduling, and gives you a pm2-style status view so you can see at a glance whether your session is actually stuck or just thinking. Has been handling my multi-day sessions without manual intervention.
Data point that isolates this to the client, since my setup has zero Anthropic infrastructure in the request path at all.
Setup: Claude Code CLI
2.1.201pointed entirely at a self-hosted model, not the real API:127.0.0.1:11434is a local Ollama instance on the same machine, servinggemma4:26b(custom taggemctx-262144,num_ctx 262144) throughllama-serveron a second loopback port. There is no network hop, noapi.anthropic.com, no Anthropic-side inference worker anywhere in this path — the entire "backend" is a single GPU in the same box: an ASRock Radeon AI PRO R9700 Creator, 32GB VRAM, ROCm 7.2.4, gfx1201.What happened: a long autonomous coding turn (89 messages in the session transcript,
turn_durationlogged at 1,864,608 ms ≈ 31 min). Partway through, the CLI's thinking/status line stopped advancing — no new output, no error, spinner just sitting there — for several minutes. It didn't crash, and sending a follow-up message ("continue") resumed it immediately.What I checked while it looked stalled:
ps auxandrocm-smion the host, at the exact moment the CLI appeared frozen, showed that thellama-serverprocess was still alive and healthy. Claude just wasn't sending it any requests.Why I think this is worth adding here: since there's no Anthropic-side server, network, or inference worker involved in my setup at all, a stall like this can't be explained by a server-side token-withholding/flush issue — there was no in-flight request for a server to stall. The backend was alive and idle, waiting; the CLI simply stopped dispatching the next request. That points at the client's own request/event-loop handling losing track of the turn, independent of what's actually serving the tokens — which lines up with the event-loop/heap-pressure theory raised earlier in this thread rather than the original server-side theory.
I can reproduce this on Windows as well, both in the CLI and the VS Code extension. It's very intermittent.
Additional data point — macOS, agent-mode, recurring; input silently dropped during the stall
Seeing this repeatedly on macOS (Darwin 25.5.0), Claude Code 2.1.197 (watchdog on), desktop 1.18286.0, model Opus 4.8, local agent-mode + ~11 MCP servers.
Symptom during a stall:
stop_reason: end_turn.claudeprocess alive but idle (~0–1% CPU), not crashed.The watchdog (#37080) doesn't catch this case — the turn had already ended cleanly, yet the session no longer ingests input. Happy to attach a
pssnapshot + transcript mtime gap, or repro under--safe-modeto isolate MCP involvement.