HTTP connections never recover after network-path change: API turns and MCP requests hang silently, session permanently unresponsive

Open 💬 0 comments Opened Jul 8, 2026 by Intoxinator

Summary

When the network path changes mid-session (observed with a VPN/Tailscale network-extension state transition on macOS), Claude Code's HTTP connections black-hole and never recover. In-flight and subsequent API turns hang for 10–20+ minutes with no error surfaced (just the spinner), and HTTP-transport MCP servers fail with characteristic timeouts. The session becomes permanently unresponsive; only restarting the session/process recovers. Other clients on the same machine (curl, Node fetch) recover immediately because they open fresh connections.

This turned a ~30-second network blip into two days of "Claude Code is broken" before the root cause was isolated.

Environment

  • Claude Code 2.1.204 (terminal install) and 2.1.202 (embedded in Claude desktop app) — both affected
  • macOS (Darwin 27.0.0), Apple Silicon
  • Trigger: Tailscale network extension in a stale state (an exit-capable peer had been offline for weeks); every VPN connect/disconnect produces a ~10–30s window where new TCP flows to some hosts black-hole (TCP ESTABLISHED, no bytes returned)

Evidence

1. Silent API hangs, no error ever logged. Session transcripts show 9–18 minute gaps between a tool_result being written and the next assistant action, with zero isApiErrorMessage entries. One headless claude -p run took 20 minutes to answer a single-word prompt. During one incident an otherwise idle CLI held ~10 ESTABLISHED plus 17 CLOSED sockets to api.anthropic.com.

2. MCP failure signatures (from mcp-logs-* debug files), affecting multiple HTTP-transport servers (Linear plugin, a claude.ai connector) at the same instant, while a stdio server (playwright) connected fine in ~140ms:

Variant A — connect phase hangs:

Starting connection with timeout of 30000ms
Testing basic HTTP connectivity to https://mcp.linear.app/mcp
[30s of silence]
Connection timeout triggered after 30003ms (limit: 30000ms)

Variant B — connect succeeds, next request dies:

Successfully connected (transport: http) in 915ms
Connection established with capabilities: {"hasTools":true,...}
Failed to fetch tools: MCP error -32001: Request timed out
HTTP connection dropped after 60s uptime

The OAuth token was valid in all cases (not an auth issue), and the endpoint answered unauthenticated curl in <1s throughout.

3. Reproduced at the runtime layer, outside Claude Code. Standalone Bun 1.3.14 fetch() to the same host hung indefinitely while curl and Node fetch from the same machine returned in ~100ms. Immediately after a VPN connect, a repeated Bun fetch battery showed the first 3 attempts to one host and the first attempt to api.anthropic.com hang (8s timeout each), then all subsequent attempts succeed in ~20ms — i.e. the runtime/pool holds and reuses dead connections rather than abandoning them.

4. Recovery asymmetry. Fresh processes always worked; the long-lived session never did. This points at per-host connection pooling / keep-alive reuse without a dead-connection eviction or retry-on-fresh-socket path.

Expected behavior

  • A request that receives no bytes for N seconds should be retried on a fresh connection, and pooled connections for that host flushed.
  • If retries fail, surface an error in the UI/transcript instead of an indefinite spinner.
  • MCP HTTP transports should reconnect with backoff after -32001/dropped-connection instead of leaving the server failed for the session.
  • Ideally: listen for network-path-change events (macOS nw_path_monitor) and flush connection pools.

Repro sketch

  1. macOS with Tailscale (or any VPN that installs/removes utun routes).
  2. Start an interactive Claude Code session; let it make a few turns (populate the connection pool).
  3. Toggle the VPN mid-session (or otherwise force a route change while requests are in flight).
  4. Observe: in-flight turn hangs indefinitely, no error; HTTP MCP servers fail as above; session never recovers without restart.

Notes

  • The /bug command itself was affected during the incident (the desktop app's feedback submission rode the same dead sockets), which is why this arrives as a GitHub issue.
  • Happy to provide sanitized log excerpts on request.

View original on GitHub ↗