VS Code extension: session hangs indefinitely on stalled SSE stream (no idle-timeout/reconnect)
Summary
Claude Code sessions in the VS Code extension intermittently hang indefinitely mid-response, with no error, no timeout, and no recovery. The process is left idle (blocked in kevent64/kqueue wait) waiting on a streaming HTTPS connection that has gone silent. There is no client-side idle-read timeout or reconnect logic, so the session — and the VS Code panel bound to it — freezes permanently until manually killed.
Environment
- Claude Code VS Code extension:
anthropic.claude-code2.1.278 - Native binary:
resources/native-binary/claude(Bun-based runtime — confirmed via thread namesBun Pool 0/1,HTTP Client,mi-scavengerin stack sample) - OS: macOS 26.5.2 (25F84), Apple Silicon (ARM64)
- Network: home Wi-Fi + wired, no corporate proxy, no VPN interception (confirmed no
HTTPS_PROXY/NODE_EXTRA_CA_CERTSenv vars, no MDM/TLS-inspection software running)
Symptom
- Start a new Claude Code session/turn in the VS Code panel.
- The turn begins normally:
[engine] turn 1 start→Stream started - received first chunklogged within ~500ms. - After the first chunk, all further progress silently stops. No more log lines (no tool calls, no additional stream chunks, no completion, no error) for minutes.
- The panel shows the session as
state: runningindefinitely. VS Code itself sometimes becomes fully unresponsive and requires a full quit/relaunch (not just "Reload Window") to recover — 3 full app relaunches observed in a 12-minute span in one incident. - This has now been reproduced twice with the identical prompt/session in back-to-back attempts.
Root cause evidence (captured live with sample)
Ran sample <pid> 3 on the stalled native-binary process mid-hang. Key findings:
- Main thread (
DispatchQueue_1: com.apple.main-thread) and a dedicated "HTTP Client" thread are both blocked inkevent64(kqueue event wait) with ~0% CPU — i.e., genuinely idle, not busy-looping, not blocked on file I/O (no APFS/read syscalls anywhere in the stack — ruled out iCloud/file-materialization as a cause). lsof/netstaton the stalled process at the time of the hang showed 4 concurrent established HTTPS connections to Anthropic's API edge (2607:6bc0::10:443), all in TCPESTABLISHEDstate with empty Recv-Q/Send-Q — no error at the TCP layer, no retransmission backlog, nothing that would surface as a socket error to the client.- All other network activity on the machine (Dropbox, Google services, Mattermost, etc.) was healthy and actively transferring data at the same moment — ruling out a general connectivity/Wi-Fi outage. No interface flap/roam events in the unified log for the relevant window.
- Ruled out: disk space / iCloud Drive file materialization stalls (no file I/O in the stack), corporate TLS interception (no proxy configured, no MDM agents running), zombie/uninterruptible processes (none found), orphaned process accumulation across restarts (none found).
Conclusion
One of the pooled HTTPS connections used for the active SSE stream goes silent — most likely due to a middlebox (e.g., home router NAT table aging out an idle "established" mapping without sending RST/FIN) dropping the path without either endpoint being notified. Since the client has no idle-read timeout and no reconnect/retry logic for a stalled stream, it waits in kevent64 forever. Nothing detects or recovers from this condition; the only recovery today is manually killing the process (or, if VS Code itself has wedged, force-quitting the whole app).
Suggested fix
- Add an idle-read timeout on the streaming HTTP response (e.g., if no chunk/keepalive received within N seconds, treat the stream as dead).
- On timeout, abort the stalled connection and automatically retry/reconnect the turn (or surface a clear "connection stalled, retrying…" state to the user) rather than hanging indefinitely.
- Consider enabling TCP keepalive with a short interval on the underlying sockets so dead connections are detected at the OS level sooner.
Attachments
Raw sample output available on request (stack trace of stalled main + HTTP Client threads in kevent64).
3 Comments
Second independent reproduction
Reproduced again in a fresh VS Code session shortly after filing this issue. Same signature, with one new detail:
sample <pid> 3again showed the main thread and the dedicated "HTTP Client" thread both parked inkevent64(kqueue wait), ~0% CPU, no progress.api.anthropic.com→160.79.104.10), and the stuck process held a genuineESTABLISHEDTCP connection to that exact IP on 443, alongside 3 more established connections to2607:6bc0::10. No connection errors, no RST, nothing — it just never got/processed a response, and the client has no timeout to detect or recover from that.This reinforces that the fix needs to cover both "stream stalls after starting" and "response never starts at all" — i.e., a timeout should apply from request-sent, not just from first-byte-received.
Third reproduction + key differential test
Third stall in ~10 minutes, 3-for-3 on fresh sessions (new PID each time, no carried-over connection pool between attempts). Same signature: main + HTTP Client threads parked in
kevent64, 0% CPU, no progress after the initial chunk.Differential test: ran a raw, unauthenticated
curlto the same host immediately after clearing the stuck process:Instant, clean TLS handshake and response — no packet loss, no path/ISP issue, no general reachability problem at the time of the hang. This is a useful negative result: it means the failure is not a generic "network is flaky right now" condition (which would also slow down/break the plain curl request). It's specific to the Claude Code client's actual authenticated streaming request — each fresh process, brand new connection, still stalls, while a simple unrelated HTTPS request to the same host succeeds trivially.
This shifts my read of the likely cause somewhat: rather than (or possibly in addition to) a NAT idle-timeout theory, this looks like it could be something in how the client negotiates/handles its specific request (e.g. HTTP/2 stream that gets accepted at the connection level but never receives a response on that particular stream) — something that would require server-side visibility (load balancer / edge logs for the specific request IDs) to fully pin down. Happy to provide the
x-client-request-idvalues from the affected turns if useful (visible in the extension's debug log as[API REQUEST] ... x-client-request-id=...).Given the 3-for-3 rate in a short window, this may currently be an elevated-frequency period rather than the rare occasional stall it seemed to be initially.
Likely version regression: 2.1.258 (works) vs 2.1.278 (hangs)
Fourth stall in ~20 minutes via the VS Code extension (which bundles native-binary
claude2.1.278). Meanwhile, a completely separate Claude Code CLI session running directly in a terminal on the same machine, same network, same Mac — using CLI version 2.1.258 (installed via/Users/georgefoulard/.local/bin/claude, not the VS Code-bundled binary) — has been running continuously for 43+ minutes with zero stalls, multiple turns, tool calls, everything working normally.Same machine, same network path (already confirmed healthy via raw curl to api.anthropic.com), same time window — the only material difference identified so far is the CLI version (2.1.258 vs 2.1.278) and the extra flags the VS Code extension passes (
--permission-prompt-tool stdio --include-partial-messages --enable-auth-status --no-chrome --replay-user-messages, etc., none of which the plain terminal invocation uses).This strongly suggests either:
--include-partial-messagesor--replay-user-messages) that changes how the streaming connection is handled/kept alive.Given the sharp contrast (0/4 vs the terminal's 0-stalls-in-43-minutes), pinning down the changelog between these two versions for the HTTP client / streaming code path seems like the fastest way to find the actual root cause.