[BUG] claude -p has no API request timeout — black-holed TCP connection hangs headless run indefinitely (4h stall, un-stuck only by sleep/wake killing the socket)

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 12, 2026

Summary

claude -p (print/headless mode) has no timeout on its API requests. When the first request's TCP connection is silently black-holed (e.g. a home router drops the idle NAT mapping mid-request, so no RST ever reaches the client), the CLI waits on the dead socket forever — it never times out, never retries, and never errors. An unattended overnight job lost 4 hours to this and was only un-stuck by a machine sleep/wake cycle that happened to kill the zombie socket.

Environment

  • Claude Code v2.1.228, subscription auth (no API key in env)
  • macOS (Darwin 25.5.0), Apple Silicon MacBook
  • Invocation: launchd LaunchAgent → zsh script → caffeinate -i claude -p "$(cat prompt.md)" --dangerously-skip-permissions
  • Machine confirmed awake and WiFi confirmed associated throughout (unified log; a lid-shut sleep-inhibitor daemon held the machine awake)

Timeline (all times local, from the macOS unified log and the session transcript)

| Time | Event |
|---|---|
| 02:07:01 | LaunchAgent fires; script launches claude -p |
| 02:07:04 | PID resolves the API hostname (mDNSResponder logs DNSServiceQueryRecord for the process) and opens its first connection |
| 02:07:05 – 06:01 | Zero further DNS queries from the PID for ~4 h (log show --predicate 'process == "mDNSResponder" AND eventMessage CONTAINS "client pid: <pid>"' over the window returns nothing). No session transcript exists yet. Process accumulates ~6 CPU-seconds total — parked, not retrying |
| 05:17 – 06:01 | Machine happens to sleep (unrelated), then wakes |
| 06:02:12 | 33 s after full wake: sudden DNS burst from the PID, reconnect succeeds |
| 06:02:13 | Session transcript begins (first queue-operation enqueue); run proceeds normally afterwards |

The wake didn't "resume" anything — the machine was awake the whole time the process was stalled. The sleep/wake cycle killed the dead TCP connection, which finally surfaced a socket error and let the CLI reconnect.

Diagnosis

A retrying client re-resolves and reconnects; this process made no new connection attempts for four hours — one DNS burst at launch, then silence. That's a single request awaiting a response on a black-holed socket with no application-level timeout above it. The likely network trigger (idle NAT-mapping drop with no RST) is common on consumer routers and unprovable after the fact; the CLI-side gap is what turns a transient network fault into an unbounded hang.

Expected behavior

  • A request-level timeout (or TCP keepalive with a dead-peer bound) on API calls in print mode, followed by normal retry/backoff.
  • Print mode is the unattended mode; an unbounded wait there means a cron/launchd job silently loses its whole window with zero output and exit-never.

Workaround

An external watchdog: poll the process's cumulative CPU time and kill/relaunch when it flatlines (a stalled CLI burns ~0 CPU; a working one burns it continuously).

Possibly related

  • #85939 (claude -p hangs with zero output on ARM64 Linux) — same symptom class, unknown if same cause.
  • #81778 (hang during thinking phase needing manual interrupt) — interactive, but consistent with a missing stall-timeout on a stream.

View original on GitHub ↗