claude -p hangs indefinitely on a silently-dead API stream (no SSE stream-idle timeout)
Summary
A non-interactive claude -p invocation can hang indefinitely when its connection to api.anthropic.com dies silently mid-stream (no FIN/RST — e.g. a NAT/middlebox idle-timeout during a long model turn). claude never detects the dead SSE stream; it produces zero output and never exits until killed by an external wall-clock timeout.
Environment
claude2.1.143 (Claude Code), macOS- Invocation (autonomous / headless):
claude -p "<prompt>" --model opus --effort high --max-budget-usd 20 --dangerously-skip-permissions, stdin =/dev/null
Forensics on a hung process
Captured live on a claude -p that had produced zero output for 30+ minutes:
ps:STAT=Ss, ~0% CPU — a brief startup CPU burst, then asleep. Not computing.- No child processes — not a hung tool subprocess; the wedge is inside
claude. lsof: two ESTABLISHED IPv6 sockets toapi.anthropic.com(2607:6bc0::10:443).netstat: those sockets show Send-Q 0 / Recv-Q 0 — the request was fully sent; nothing is coming back.sample: theHTTP Clientthread is parked 100% inkevent64; the main event loop is idle.- A fresh
curltoapi.anthropic.com(IPv4 and IPv6) connects + responds in ~10 ms — the network path is healthy. Only the long-lived connectionclaudeis holding has gone stale.
API_TIMEOUT_MS does not cover this
API_TIMEOUT_MS (default 600000 ms) is a total-request timeout. It did not fire on this mid-stream stall — the hung process held the same two sockets across observations 4+ minutes apart, both well past the 10-minute mark. There appears to be no stream-idle timeout, and CLAUDE_CODE_MAX_RETRIES never engages because the request never fails — it just hangs.
Expected behavior
claude -p should detect a stalled SSE stream — no bytes received for a (configurable) interval — and abort + retry. A fresh connection works immediately, so the existing retry path would recover the request. Today it waits forever.
Why it matters
For any automated/headless use of claude -p (CI, agents, scheduled jobs), a single dead-stream event is an indefinite hang that only an external timeout wrapper can break. A stream-idle timeout would turn an unrecoverable hang into a recoverable transient.
Request
Add an SSE stream-idle timeout to the API client used by claude -p — abort + retry a stream that has received no bytes for a configurable interval (e.g. an API_IDLE_TIMEOUT_MS env var).
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗