Skip to content

[BUG] Claude Code hangs indefinitely when API streaming connection stalls (no read timeout) #25979

Description

@esuleman

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

Claude Code hangs permanently when the API streaming response stalls mid-delivery. The process stays alive (epoll_wait in kernel) but makes no progress. The UI shows a spinner ("Accomplishing…",
"Ruminating…", etc.) indefinitely. No error is surfaced. The session cannot recover — the only fix is kill -9.

Two distinct hang patterns observed:

Pattern 1 — Mid-turn stream freeze: The API response starts streaming (thinking block received, "thought for 3s" renders), then the stream silently stops. No more tokens arrive. The JSONL session
log shows no new entries after the last progress update. The process is stuck waiting for bytes that never come.

Pattern 2 — Tool result delivery stall: A Bash tool executes and completes (e.g., curl --max-time 5), but the tool result is never delivered back to the conversation state. The last tool_use ID in
the JSONL has no matching tool_result. The UI shows the spinner as if the tool is still running.

Both patterns correlate with background agent (Task with run_in_background: true) notifications arriving between or during turns.

What Should Happen?

  1. Claude Code should have a read timeout on the SSE/streaming HTTP response. If no data arrives within N seconds (e.g., 60–120s), it should abort the request and surface a retryable error to the
    user.
  2. Tool result delivery should have a timeout. If a tool completes but the result isn't consumed within N seconds, the session should error rather than hang.
  3. The UI should detect "no progress for N seconds" and offer the user an escape (e.g., "Session appears stuck. Press Enter to retry or Esc to cancel").

Error Messages/Logs

Session 1: Mid-turn stream freeze (fff87be2)

  JSONL timeline:
  21:43:28 UTC  Turn 1 ends (system/turn_duration logged)
  21:43:28      Background agent notification injected as user message
                → Triggers Turn 2
  21:44:44      Turn 2 ends (system/turn_duration logged, stop hooks run)
                Background agent notification for a DIFFERENT agent arrives
                → Triggers Turn 3
                API call starts, "thought for 3s" renders in UI
                *** No more JSONL entries. Stream froze. ***
                UI stuck on "Accomplishing… (thought for 3s)" for 10+ minutes

  Process state during hang:
  $ ps -p 843072 -o pid,state,wchan
      PID S WCHAN
   843072 S do_epoll_wait

  $ kill -TERM 843072   # no effect
  $ kill -KILL 843072   # required to terminate

  Session 2: Tool result delivery stall (06d50a72)

  JSONL shows:
  Last tool_use ID:    toolu_vrtx_01JqgVUPZLrMyudEQaAQhx1x  (Bash: curl)
  Last tool_result ID: toolu_vrtx_01LJscxroh9R7tsDQUL6AhuF  (different, earlier tool)
  → Mismatch: the curl's result was never delivered

  The curl command had --max-time 5 so it completed, but the result never made it back to the conversation. UI showed "Ruminating…" indefinitely.

Steps to Reproduce

Difficult to reproduce deterministically — it appears to be a race condition or network-level issue. But the following pattern triggers it frequently:

  1. Start Claude Code on a remote Linux server via SSH + tmux
  2. Give it a complex task that spawns multiple background agents (Task with run_in_background: true)
  3. Wait for background agents to complete and deliver notifications
  4. When a notification arrives right as a turn is ending or between turns, the next API call has a high chance of hanging

Environment factors that may contribute:

  • Remote server (high-latency network path to API)
  • tmux (terminal multiplexing)
  • Multiple concurrent Claude Code sessions in different tmux panes
  • Heavy hook infrastructure (6 hook events, though all complete in <120ms per telemetry)

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

2.1.40

Claude Code Version

2.1.42

Platform

Google Vertex AI

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

Suggested fix

Add a read timeout to the HTTP streaming client. Pseudocode:

// In the SSE/streaming response handler:
const STREAM_READ_TIMEOUT_MS = 120_000; // 2 minutes

let lastDataTime = Date.now();
stream.on('data', (chunk) => {
lastDataTime = Date.now();
// ... process chunk
});

const watchdog = setInterval(() => {
if (Date.now() - lastDataTime > STREAM_READ_TIMEOUT_MS) {
stream.destroy(new Error('API stream read timeout'));
clearInterval(watchdog);
}
}, 10_000);

Workaround

External watchdog daemon that monitors JSONL session files and kills processes with no writes for >5 minutes. Available at user's request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api:vertexIssue relates to GCP VertexbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions