`API Error: The socket connection was closed unexpectedly` — packet captures show server-initiated FIN mid-stream (10 incidents, requestIds included)

Open 💬 5 comments Opened Jun 12, 2026 by raphaelabreu1

Summary

Claude Code intermittently kills active turns with:

API Error: The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()

This happens ~8–18 times per day under heavy interactive use. I instrumented the environment (rotating tcpdump + ss socket-state logging + a transcript watcher) and captured 10 incidents with full packet evidence, across four different sessions/windows. All 10 show the same signature:

The remote side closes the TCP connection with a clean FIN while the response is actively streaming. The CLI surfaces the error 3–105 ms after the FIN and the turn is lost; a fresh connection opened immediately afterwards works fine.

This is not the usual "stale idle connection" story — evidence below.

Environment

  • Claude Code 2.1.145 → 2.1.173 (reproduces across all versions in this window; the captured incidents ran 2.1.172 and 2.1.173)
  • VS Code Dev Container on Docker Desktop (kernel 6.12.x-linuxkit), Debian 11
  • Direct connection to api.anthropic.com (160.79.104.10) — DNS resolution, TLS certificate (CN=api.anthropic.com, Google Trust Services) and ~40 ms round-trip verified clean; no proxy / no MITM on this path, no HTTP(S)_PROXY set
  • TCP keepalive is enabled on the CLI's sockets (~50 s timer, verified via ss -o) — so this is not the missing-SO_KEEPALIVE scenario

Signature (identical in 10/10 captured incidents)

  1. Server-initiated, graceful FIN on the exact TCP connection carrying the in-flight request. Never an RST from a middlebox, never a client-side close, zero retransmissions before the event.
  2. Mid-stream: the request body (1–2.5 MB) had been fully ACKed seconds earlier and the SSE response was actively streaming — 7–20 KB already delivered and content blocks already persisted by the CLI — when the FIN arrived. No terminal SSE event / error frame was received (otherwise the CLI would have surfaced that instead).
  3. Selective: other pooled connections to the same destination remained ESTABLISHED through the event (verified in 5-second ss snapshots). In incident #6, per-PID socket attribution shows the FIN hit exactly the connection owned by the requesting CLI process while connections owned by two other concurrently running claude processes survived untouched. This rules out local NAT, Docker networking, or machine-level network failure.
  4. Instant recovery: the CLI opens a new connection within ~20 ms and the next request succeeds. The cost is the lost turn — partial response discarded, user must re-prompt.

Four incidents (#4, #7, #8, #10) additionally captured server-side drain waves — batches of FINs hitting several pooled connections (across multiple independent CLI processes) within a few hundred ms to ~90 s. In incident #4 the wave included one FIN immediately followed by RST (win 0) and one connection where ~26 KB of in-flight client upload was answered with a burst of 18 RSTs. In every wave, only the connections with an in-flight stream surfaced user-visible errors; idle ones died silently. Incidents #7, #8 and #9 fired at second :54 of nearby minutes (01:19:54, 01:20:54, 01:22:54 UTC) — a stable 60 s sweep phase — while #10 broke the phase, so the closes recur at minute-scale intervals with a non-fixed phase.

Two further data points: (a) during a 14-minute burst (5 incidents, 01:10–01:25 UTC on 2026-06-12), status.claude.com reported all components operational — this is chronic, sub-threshold behavior, not a declared outage; (b) in some incidents the CLI's retry recovered the turn transparently, in others the turn was lost and required a manual re-prompt — the damage depends on where in the turn the FIN lands.

Traceable requestIds (UTC)

| # | requestId | Error logged at | FIN → error gap |
|---|---|---|---|
| 1 | req_011CbxEk13e5XAKmG4GwvQw3 | 2026-06-11 22:10:34.909Z | 24 ms |
| 2 | req_011CbxHiBmrXDFWLrBKLHLCL | 2026-06-11 22:49:39.819Z | 2.4 ms |
| 3 | req_011CbxKGVEbLhFv3W5TxTdRz | 2026-06-11 23:10:40.124Z | 105 ms |
| 4 | req_011CbxKexjRqhJsHaxXShsT7 | 2026-06-11 23:15:09.491Z | 3 ms |
| 5 | req_011CbxMiTSti9KUcdoiQDZDi | 2026-06-11 23:42:12.223Z | 12 ms |
| 6 | req_011CbxUTz53C6nVahGAygovR | 2026-06-12 01:10:55.601Z | 17 ms |
| 7 | req_011CbxV68JtDnpPGRCf56ED2 | 2026-06-12 01:19:54.074Z | 10 ms |
| 8 | req_011CbxVFutYvCCpvfgLZ4KYo | 2026-06-12 01:20:54.067Z | 50 ms |
| 9 | req_011CbxVPtdsJpnjd58E1b2yS | 2026-06-12 01:22:54.575Z | 6 ms |
| 10 | req_011CbxVWHA7aD7ux6svfT4fc | 2026-06-12 01:24:24.772Z | 5 ms |

Example — incident #3, packet-level timeline

23:09:48.214  Out  172.20.0.4.54120 > 160.79.104.10.443  [S]            ← connection opened (new user turn)
23:09:48.223  In   160.79.104.10.443 > 172.20.0.4.54120  [S.]
              ... ~1.4 MB request uploaded, fully ACKed, zero retransmits ...
23:10:40.012  (CLI persists streamed assistant content — response is flowing, ~15 KB down)
23:10:40.019  In   160.79.104.10.443 > 172.20.0.4.54120  [F.]           ← SERVER closes, mid-stream
23:10:40.019  Out  172.20.0.4.54120 > 160.79.104.10.443  [F.]           ← client acknowledges close
23:10:40.124  CLI: "API Error: The socket connection was closed unexpectedly..."

(172.20.0.4 is the container's private address.)

Frequency / what was ruled out

Parsed isApiErrorMessage entries across 23 days of local session transcripts: 200 errors / 171 incidents.

  • Not idle-related: 87/171 incidents occurred < 5 s after previous API activity (mid-task); only 7 after > 1 min.
  • Not a version regression: uniform across 2.1.145 → 2.1.173.
  • Not local concurrency: error rate per session-minute is the same whether 1 or 2+ Claude Code windows are active (27.9 vs 24.7 errors/1000 session-minutes).
  • Not the local network path: verified no proxy/MITM, healthy latency, and survivor connections during every incident.
  • 84% of incidents recover on the first fresh connection.

Asks

  1. Could you trace the requestIds above server-side? What closed these connections mid-stream — LB connection-lifetime/drain policy, deploy rollout, backend crash?
  2. If edge connection recycling is expected behavior, could the CLI handle it gracefully (transparent retry/stream resume on FIN/GOAWAY mid-stream) instead of failing the user's turn? The request is identifiable and in all captured cases the stream was only a few content blocks deep.

Methodology (reproducible)

  • Packet ring (headers only): sudo tcpdump -i any -nn -U -s 128 -C 10 -W 5 -w ring.pcap 'tcp and net 160.79.104.0/24'
  • Socket state every 5 s: ss -tinoep dst 160.79.104.0/24
  • Watcher tailing ~/.claude/projects/**/*.jsonl for new isApiErrorMessage entries, correlating each error timestamp against the capture

Full pcap slices for each incident are archived and available on request.

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗