[BUG] CLI hangs indefinitely on stuck streaming response — UI swallows input but redraws on SIGWINCH; only external kill recovers
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?
The CLI enters a permanent "soft-hang" state mid-conversation:
- The in-flight streaming response to the Anthropic API never completes.
- Keyboard input (including Ctrl+C and Esc) is silently consumed — no echo, no visible effect.
- The TUI still redraws on window resize (SIGWINCH), so the process is clearly not crashed.
- The only way out is
kill -INT/-TERM/-9from a separate terminal.claude --resumeafter that recovers the session intact.
I've hit this many times in the last few weeks. It reproduces reliably after the laptop sleeps/wakes or after a local network/proxy reconnects while a streaming response is in flight. No client-side inactivity timeout appears to fire — I've left a wedged session sitting for 45+ minutes with zero recovery.
What Should Happen?
What Should Happen? (required)
A streaming request that stalls (no bytes received for some inactivity window) should be aborted automatically, the user should be shown an error like "stream stalled, retry?", and the TUI should return to an interactive prompt — without requiring the user to kill the process from another terminal.
At minimum, Ctrl+C (or Esc) should reliably cancel an in-flight request even when the response stream is wedged.
Error Messages/Logs
# Hung process — alive, idle, single dangling socket:
$ ps -p 3771 -o pid,state,etime,%cpu,wchan,command
PID STAT ELAPSED %CPU WCHAN COMMAND
3771 S+ 45:45 1.0 - claude --resume
$ lsof -p 3771 -nP | grep TCP
claude 3771 ... TCP 198.18.0.1:54969 -> 198.18.0.28:443 (ESTABLISHED)
# (Healthy claude processes on the same machine have 3–5 rotating sockets;
# the hung one has exactly one, on the dead stream.)
# sample(1) — main thread parked on kevent64, "HTTP Client" Bun thread also on kevent64:
2488 Thread_xxx DispatchQueue_1: com.apple.main-thread (serial)
... kevent64 (in libsystem_kernel.dylib) + 8 [0x18d9cbba8]
2488 Thread_xxx: HTTP Client
... kevent64 (in libsystem_kernel.dylib) + 8 [0x18d9cbba8]
# No CPU, no work pending — event loop is alive but every thread is idle waiting on I/O.
Steps to Reproduce
- Start a session in a terminal:
claude --resume - Send a prompt that yields a long streaming response.
- While the response is streaming, cause the underlying TCP connection to silently die. Reliable triggers in my setup:
- Sleep/wake the laptop
- Reconnect / switch nodes in a local VPN/proxy (fake-IP routing in my case, but I believe any network event that drops the TCP without sending FIN/RST will do — e.g. WiFi roam, NAT timeout)
- The TUI is now frozen:
- Typing produces no echo and no UI reaction
- Ctrl+C / Esc do nothing
- Resizing the terminal window DOES trigger a clean repaint (SIGWINCH path works)
- No timeout fires. Observed > 45 minutes wedged.
- Recovery requires
kill -INT <pid>(or-TERM/-9) from another terminal, thenclaude --resume.
Why this happens (best guess)
- The event loop is healthy — SIGWINCH triggers a full Ink re-render.
- stdin (raw mode,
isigdisabled) is still being read, but the keystroke handler is awaiting a promise that never resolves — that promise is the streaming response. - The TCP looks
ESTABLISHEDlocally because the kernel never received FIN/RST (a proxy/NAT dropping the upstream after sleep/wake or a peer crash without an RST will produce exactly this state). - There is no client-side inactivity / read timeout on the response stream, so the
for await (chunk of stream)loop never gives up. - Because raw mode disables
isig, keyboard Ctrl+C is delivered as byte 0x03 (not SIGINT), so it goes through the same wedged input pipeline. Only an externalkill -INTreaches the process.
Suggested fixes (in order of impact)
- Stream inactivity timeout on the API client: if no bytes received for N seconds (configurable, e.g. 90s), abort the request and surface a retry prompt. This alone eliminates the hang.
- Always-available cancel keybind: route Ctrl+C through a synchronous abort path, not through the awaiting state machine.
- TCP keepalive on outbound sockets (
socket.setKeepAlive(true, 30000)) — bounds the half-open window to ~75s after probing starts. - Heartbeat indicator while waiting on bytes (a moving dot every few seconds), so "waiting" vs "frozen" is distinguishable to the user.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.132 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Other
Additional Information
Additional Information (optional)
- macOS 26.4.1 (Darwin 25.4.0), Apple Silicon.
- Terminal: Ghostty (not in the dropdown — selected "Other").
- A local VPN/proxy is in use (fake-IP routing via 198.18.0.0/15), which I believe is the trigger but not the root cause: any TCP that dies without FIN/RST should produce the same symptom, since the client has no inactivity timeout to catch it.
Not duplicates of:
- #59810 (general timeout detection feature) — related direction, but this is a concrete reproducible bug, not a feature ask.
- #59827 (
goalfunction loop) — different layer. - #59750 / #59899 / #59814 (Windows TUI unresponsive) — different platform/path.
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗