Skip to content

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

Description

@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 AWS Bedrock support #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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:networkingbugSomething isn't workingduplicateThis issue or pull request already existshas reproHas detailed reproduction stepsplatform:linuxIssue specifically occurs on Linux

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions