API Error: The socket connection was closed unexpectedly. + SOLUTION FOR ANTHROPIC DEVS
Bug: "Socket connection was closed unexpectedly" during long agentic sessions
Summary
Claude Code CLI throws API Error: The socket connection was closed unexpectedly consistently
during long agentic tasks. The error is systemic — it occurs across all projects, prompts, and
directories, always mid-session, never at startup. The session must be fully restarted to recover.
---
Environment
- Claude Code version: 2.1.143
- OS: Arch Linux (Manjaro), kernel 6.12.85-1
- Install method: Official installer (
curl | bash) + pacman/AUR (same binary, both 2.1.143) - Binary runtime: Bun 1.3.14 / Node v24.3.0 (confirmed via
stringson the binary)
---
Error
API Error: The socket connection was closed unexpectedly.
For more information, pass `verbose: true` in the second argument to fetch()
The error message itself is a raw Bun/JSC runtime string (hardcoded in the JSC engine section
of the binary). It surfaces with no actionable guidance and no automatic retry.
---
Root Cause Analysis
Finding 1: Bun does not set SO_KEEPALIVE on its TCP sockets
Confirmed live via ss -tnop state established '( dport = :443 )':
# Every other application on the system:
chrome → 34.194.3.76:443 timer:(keepalive,16sec,0) ← SO_KEEPALIVE ON
code → 4.228.31.153:443 timer:(keepalive,39sec,0) ← SO_KEEPALIVE ON
# Claude Code (before workaround):
claude → 160.79.104.10:443 (no timer) ← SO_KEEPALIVE NOT SET
claude → 2607:6bc0::10:443 (no timer) ← SO_KEEPALIVE NOT SET
Without SO_KEEPALIVE, idle TCP connections receive no probe packets. During agentic tasks,
the HTTP/2 connection sits idle while tools execute locally. That idle window is long enough
for the Anthropic server, Cloudflare, or intermediate router NAT to silently drop the connection.
When Claude attempts the next API call on the dropped connection, Bun throws the error.
Finding 2: All application-level keepalives are disabled via GrowthBook feature flags
Inspecting ~/.claude.json (cachedGrowthBookFeatures):
"tengu_bridge_poll_interval_config": {
"heartbeat_interval_ms": 0,
"session_keepalive_interval_ms": 0,
"session_keepalive_interval_v2_ms": 0
}
All three keepalive/heartbeat intervals are 0. There is no fallback mechanism at the
application layer when the TCP layer provides none.
The environment variable CLAUDE_CODE_REMOTE_SEND_KEEPALIVES exists in the binary and
appears to override this, but it is not documented and users cannot be expected to discover it.
Finding 3: Stale connection retry only fires for background agents, not CLI
Binary strings show two distinct code paths:
tengu_streaming_stale_connection_retry ← exists for background agents
cli_nonstreaming_fallback_started ← exists for CLI
Error streaming (non-streaming fallback disabled):
In practice, the CLI path does not recover — the error propagates directly to the user with
no retry. Background agents have a retry path; CLI sessions do not.
Finding 4: NODE_OPTIONS=--dns-result-order=ipv4first is not fully honored by Bun
After setting NODE_OPTIONS=--dns-result-order=ipv4first as a workaround, active connections
still include IPv6:
192.168.1.150:36660 → 160.79.104.10:443 ← IPv4 (api.anthropic.com)
[2804:...]:47658 → [2607:6bc0::10]:443 ← IPv6 (api.anthropic.com) — still present
[2804:...]:56560 → [2600:1901:0:3084::]:443 ← IPv6 (other Claude service)
Bun uses its own native DNS resolution for some code paths, bypassing Node.js's dns module
settings. The flag has partial effect only.
Finding 5: Error persists after forcing SO_KEEPALIVE via LD_PRELOAD
As a workaround, SO_KEEPALIVE was forced on all sockets via an LD_PRELOAD shim:
int socket(int domain, int type, int protocol) {
int fd = orig_socket(domain, type, protocol);
if (fd >= 0 && (type & 0xf) == SOCK_STREAM) {
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
}
return fd;
}
After applying this, ss -tnop confirms keepalive timers are now active on all claude sockets.
A stress test (two consecutive 90-second idle windows) passed successfully.
However, the error continued to occur during shorter tasks. This indicates a second,
independent cause: the Bun HTTP/2 client does not gracefully handle server-initiated connection
closure (HTTP/2 GOAWAY frame or TCP RST) and surfaces it as a fatal unrecoverable error
instead of transparently retrying on a new connection.
---
Reproduction
Run a long agentic task with multiple tool-call turns and significant idle time between turns:
claude --dangerously-skip-permissions -p "Run one at a time:
1. date
2. python3 -c \"import time; time.sleep(90); print('survived 90s')\"
3. python3 -c \"import time; time.sleep(90); print('survived second 90s')\"
4. date"
Without fixes: fails during step 2 or 3 with the socket error.
With LD_PRELOAD SO_KEEPALIVE fix: passes (3m21s, both sleeps survived).
During normal interactive agentic use: still fails intermittently even with the fix.
---
Suggested Fixes (for Anthropic)
Fix 1 — Set SO_KEEPALIVE on API sockets (one line, highest impact):
socket.setKeepAlive(true, 30000); // or equivalent in Bun's net API
This is the single most impactful change. Chrome and VS Code both do this; Claude Code does not.
Fix 2 — Enable heartbeat_interval_ms by default for CLI sessions:
The GrowthBook flag currently forces 0 for all keepalive intervals. CLI sessions should
have a non-zero default (e.g. 30s) independent of the server-side feature flag value.
Fix 3 — Implement transparent retry on stale connection for CLI:
The background agent path (tengu_streaming_stale_connection_retry) already handles this.
The CLI path should get the same treatment instead of surfacing a fatal error.
Fix 4 — Handle HTTP/2 GOAWAY gracefully:
When the server closes an HTTP/2 connection (normal lifecycle behavior), Bun should
transparently open a new connection and retry the request rather than propagating the
low-level socket error to the user.
Fix 5 — Improve the error message:"pass verbose: true in the second argument to fetch()" is a raw Bun internal error.
Users cannot act on this. At minimum it should say the session can be resumed with --continue.
---
Workaround (for users until fixed)
Add to ~/.zshrc or ~/.bashrc:
export CLAUDE_CODE_REMOTE_SEND_KEEPALIVES=true
export BUN_CONFIG_HTTP_IDLE_TIMEOUT=300
export BUN_CONFIG_HTTP_RETRY_COUNT=3
export CLAUDE_STREAM_IDLE_TIMEOUT_MS=120000
export NODE_OPTIONS="--dns-result-order=ipv4first"
For a more reliable fix, also apply the SO_KEEPALIVE shim:
cat > /tmp/ka.c << 'EOF'
#define _GNU_SOURCE
#include <stddef.h>
#include <sys/socket.h>
#include <dlfcn.h>
int socket(int domain, int type, int protocol) {
static int (*orig)(int,int,int);
if (!orig) orig = dlsym(RTLD_NEXT, "socket");
int fd = orig(domain, type, protocol);
if (fd >= 0 && (type & 0xf) == SOCK_STREAM) {
int v = 1;
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &v, sizeof(v));
}
return fd;
}
EOF
gcc -shared -fPIC -O2 -o ~/.local/lib/libkeepalive.so /tmp/ka.c -ldl
echo 'export LD_PRELOAD="$HOME/.local/lib/libkeepalive.so${LD_PRELOAD:+:$LD_PRELOAD}"' >> ~/.zshrc
Also lower the TCP keepalive probe interval (requires sudo):
sudo sysctl -w net.ipv4.tcp_keepalive_time=60
echo "net.ipv4.tcp_keepalive_time=60" | sudo tee /etc/sysctl.d/99-claude-keepalive.conf
---
Evidence References
| Finding | Method | Confirmed |
|---|---|---|
| No SO_KEEPALIVE on claude sockets | ss -tnop live output | ✅ |
| All keepalives at 0ms | ~/.claude.json GrowthBook cache | ✅ |
| Binary is Bun 1.3.14 | strings on binary | ✅ |
| Error is Bun/JSC hardcoded string | strings on binary, line 42426 | ✅ |
| stale-connection retry missing for CLI | strings code path analysis | ✅ |
| IPv6 connections despite ipv4first flag | ss -tnop after applying NODE_OPTIONS | ✅ |
| Error persists after SO_KEEPALIVE forced | Live reproduction during diagnostic session | ✅ |
| 90s idle stress test passes with shim | Controlled test, two 90s windows | ✅ |
22 Comments
Found 2 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Cross-issue index + evidence that this is a server-side regression, not a user environment problem
After reviewing all related open issues and their workarounds, I want to make the case that this is a server-side regression affecting all users, and that the root cause and fix are now fully understood.
---
Related issues — same root cause, different symptoms
| Issue | Platform | Status | Workaround found by users |
|---|---|---|---|
| #5674 | macOS | Open (38 comments, since Aug 2025) | Cloudflare Warp VPN, MTU reduction, removing Tailscale |
| #28557 | macOS/cross | Open (10 comments) | Disabling IPv6, MTU to 1492 |
| #37077 | macOS | Open | None |
| #49761 | macOS | Open | None |
| #52899 | macOS | Open, stale | None |
| #54287 | cross | Closed as duplicate | None |
| #56017 | macOS | Open | None |
| #56059 | macOS | Open | Full system reboot |
| #60133 (this issue) | Linux | Open | Full root cause identified — see below |
Every single one of these is the same failure: an idle HTTP/2 TCP connection is silently dropped, and Claude Code crashes instead of reconnecting.
The reason they look like different problems (VPNs, MTU, IPv6, Docker, Tailscale) is that users are accidentally changing their network routing in ways that affect how quickly idle connections get dropped — not fixing the actual cause.
---
Why users think it's their environment (it isn't)
api.anthropic.comresolves IPv6-first viagetaddrinfo. IPv6 paths through home routers tend to have shorter stateful firewall idle timeouts than IPv4 NAT. Switching to IPv4 just buys more time before the same drop occurs.tcp.mssdflt=512, causing packet fragmentation. But Cloudflare Warp also fixes it — because Warp's tunnel enforces its own keepalive, compensating for the missingSO_KEEPALIVEin the Claude binary.blackhole=1, shorter NAT timeouts) that expose the missing keepalive faster. On Linux the connection survives slightly longer, but the same drop eventually happens.---
The actual root cause (confirmed via binary analysis on Linux)
The Claude Code binary (Bun 1.3.14) does not set
SO_KEEPALIVEon its TCP sockets.Confirmed live via
ss -tnop:Without
SO_KEEPALIVE, the kernel never sends probe packets on idle connections. The HTTP/2 TCP connection is shared across multiple request/response cycles. Between API turns (while tools execute locally), the connection sits completely idle. After enough idle time, the server or any stateful device in the path silently closes it. Bun discovers this only when it tries to write to the dead socket → "socket connection was closed unexpectedly."This is compounded by a server-side configuration found in
~/.claude.json(GrowthBook feature flags):All application-level keepalives are explicitly set to zero. The binary has the infrastructure for keepalives (
CLAUDE_CODE_REMOTE_SEND_KEEPALIVESenv var, heartbeat timer, session keepalive timer), but the server-side feature flags disable them entirely. This appears to be a regression introduced around v2.1.126→v2.1.143, consistent with @romancone's report that 2.1.126 worked and 2.1.143 does not — the binary code didn't change, the feature flags did.---
Why this is Anthropic's fix to make (not users')
The fix on Anthropic's side is a one-liner:
Set
SO_KEEPALIVEon the API socket. That's it. Chrome does it. VS Code does it. Every HTTP client library does it by default. The Claude binary doesn't.Additionally:
heartbeat_interval_ms: 0removes the last fallback. Even a 30-second heartbeat would prevent the majority of these failures.tengu_streaming_stale_connection_retryfor background agents. CLI sessions need the same path instead of surfacing a fatal unrecoverable error.---
Working workaround for all affected users (Linux and macOS)
For a complete OS-level fix that forces
SO_KEEPALIVEon every TCP socket Claude opens:Verified: After applying this, all Claude sockets show
timer:(keepalive,Xs,0)inss -tnop, and two consecutive 90-second idle windows survive without error (3m21s stress test passed).Full diagnostic report, binary analysis, evidence table, and all commands: #60133
Proposed Solution
Root Cause
Two independent causes:
SO_KEEPALIVE— Bun doesn't set keepalive on TCP sockets, idle connections are silently droppedtengu_streaming_stale_connection_retry, CLI doesn'tFix 1: Set
SO_KEEPALIVEon API sockets (one line, highest impact)Fix 2: Enable heartbeat keepalive by default for CLI
Fix 3: Implement transparent retry on stale connection for CLI
Fix 4: Improve error message
Replace raw Bun error with actionable guidance: "Connection lost. Try
claude --continueto resume."Impact
Full solution:
solutions/claude-code-60133-socket-connection-closed-fix.md@jshaofa-ui Your analysis is correct and aligns exactly with what we confirmed through live diagnostics on this issue.
A few notes from our investigation:
socket.setKeepAlive(true, 30000)would resolve the majority of cases. Chrome and VS Code both do this; Claude Code does not.heartbeat_interval_ms: 0,session_keepalive_interval_ms: 0) override everything at the application layer. CLI sessions need a non-zero default that is independent of server-side flag values.tengu_streaming_stale_connection_retry; the CLI path just propagates the error with no recovery.pass verbose: true in the second argument to fetch()) is a raw Bun internal string that users cannot act on at all.We also confirmed via
ss -tnopthatNODE_OPTIONS=--dns-result-order=ipv4firsthas partial effect only — Bun uses its own native DNS resolution for some code paths and bypasses the Node.js dns module entirely.While Anthropic works on the proper fix, we've put together a user-side workaround script that applies all four mitigations (env vars, SO_KEEPALIVE LD_PRELOAD shim, sysctl tuning, persistence across reboots) for both Linux and macOS. Posting it in the next comment.
User-side Workaround Scripts
---
Two scripts — an installer and a full revert. Each phase is optional and prompts for
Y/nbefore making any change.Tested on: Arch Linux (Manjaro) kernel 6.12, Claude Code 2.1.143, Bun 1.3.14. macOS paths and compile flags are included but less battle-tested.
---
Install:
claude-keepalive-fix.shWhat each phase does:
| Phase | Change | Reversible |
|-------|--------|-----------|
| 1 | Adds
CLAUDE_CODE_REMOTE_SEND_KEEPALIVES,BUN_CONFIG_HTTP_IDLE_TIMEOUT, etc. to your shell config | ✅ by uninstall script || 2 | Compiles a ~15KB C shim that forces
SO_KEEPALIVE=1on every TCP socket viaLD_PRELOAD| ✅ by uninstall script || 3 | Sets
net.ipv4.tcp_keepalive_time=60(Linux) or fixesmssdflt/blackhole(macOS) viasysctl| ✅ resets on reboot or via uninstall || 4 | Persists the sysctl value across reboots | ✅ by uninstall script |
<details>
<summary><strong>claude-keepalive-fix.sh (click to expand)</strong></summary>
</details>
---
Uninstall / Revert:
claude-keepalive-uninstall.shRun this if Anthropic ships a fix or if you want to roll back all changes:
<details>
<summary><strong>claude-keepalive-uninstall.sh (click to expand)</strong></summary>
</details>
---
After running the install script, open a new terminal (or
source ~/.zshrc) and verify with:Stress test to confirm it works:
Thanks for your analysis.
Still not fixed
Confirming this reproduces on macOS 26.2 Tahoe (Darwin 25.2.0), Claude Code 2.1.145, with the full mitigation set from this thread applied:
net.inet.tcp.always_keepalive=1,keepidle=60000,keepintvl=30000(macOS equivalent of the LinuxLD_PRELOADshim — no binary injection needed)CLAUDE_CODE_REMOTE_SEND_KEEPALIVES=true,BUN_CONFIG_HTTP_IDLE_TIMEOUT=300,BUN_CONFIG_HTTP_RETRY_COUNT=3,CLAUDE_STREAM_IDLE_TIMEOUT_MS=120000mssdflt=1440,delayed_ack=0,blackhole=0)NODE_OPTIONS=--dns-result-order=ipv4firstdeliberately omitted (breaks Happy Eyeballs on macOS)Verified the local network path is healthy: 0/10 failures on both IPv4 (
160.79.104.10) and IPv6 (2607:6bc0::10) sustained tests, all responses ~50ms.Idle-drop failures are gone, but ECONNRESET mid-session continues — matching Finding 5 (Bun HTTP/2 not handling server-initiated
GOAWAY/RST). Symptom is constant retry loops during normal active agentic work, not just long-idle sessions.Two notes for triage:
platform:linux\label is misleading — this affects macOS identically once the user has exhausted kernel-level workarounds. Suggest broadening to \platform:macos\as well or removing the platform restriction.tengu_streaming_stale_connection_retry\path or graceful \GOAWAY\handling in the Bun fetch wrapper leaves this unresolved.Yeah folks, my workarounds does not fix it completely, but it’s because of an Anthropic API internal issue. I was actually the one who originally reported this problem to Anthropic.
I strongly suspect this issue may only be affecting PRO users. One of the reasons I believe that is because the exact same behavior also happens retroactively on older Claude Code CLI versions, which makes it feel less like a recent client-side regression and more like something happening at the API or infrastructure layer.
What also makes this suspicious to me is that there should not really be a reason for API response behavior and runtime instability to differ this drastically between subscription tiers under normal circumstances.
Anthropic acquired Bun in December of last year:
https://www.anthropic.com/news/anthropic-acquires-bun-as-claude-code-reaches-usd1b-milestone
Initially, the reported failures looked like standard Node.js runtime errors, but more recently the stack traces and failure patterns have started resembling Bun-specific behavior.
Of course, this could still be a simple engineering mistake, infrastructure regression, or an unintended side effect of internal changes. But I have my doubts. I’m curious how many people here are actually PRO users, because if this issue is confirmed to disproportionately affect PRO accounts, then this could become a major community concern.
Important note: this is ultimately my personal opinion and speculation based on my observations. I may be completely wrong. However, what is evident is that reports about this problem appear to have been dismissed since last year, back when the runtime behavior looked Node.js-related, and now again while the behavior appears more consistent with Bun.
cc: @cubicYYY @muxunting @searchingtofind @jshaofa-ui
I have a max subscription, and this hit me suddenly, and across multiple long persisting sessions, without any anthropic side update pushed, which is, odd, to say the least. I have been using max subscription for 10 months, without anything like this happening. With The third party API changes coming mid june... it did feel targeted for some reason...
Yeah @searchingtofind bro, same feeling.
Adding some complementary evidence in case it's useful — independently hit the same symptom on macOS (Brazilian residential ISP) and went through a similar investigation. Used Claude Code itself to help dig through pcaps and traces, so flagging upfront that some of my interpretation may be AI-shaped pattern-matching rather than ground truth.
What I can add on top of @brunos3d analysis
1. Packet-level evidence that some mid-stream breaks are middlebox-injected, not just lifecycle-related
I ran
tcpdumpofapi.anthropic.comtraffic in parallel withclaude --debug --debug-fileacross several sessions. The mid-stream socket closes that survive Fix 1 (SO_KEEPALIVE) and would need Fix 3/4 to handle are, in my case at least, spoofed RSTs from a middlebox on the network path, not HTTP/2 GOAWAY or natural server-side closure.Packet excerpt from one of the breaks (IPv4 path, request
64655465-9cbb-4691-beea-8b87a80ed18d, 2026-05-24T22:39:14Z):Two impossible-if-real-server fingerprints:
Same fingerprint reproduced on the IPv6 path (request
8a91f305-6768-4721-bf2f-4d7bf63c5313, RST seq ~1.95 billion vs legitimate flow at 8,516).This is presumably an ISP DPI box on my path — not Anthropic's fault. But it strengthens the case for Fix 3 (transparent retry on the CLI path), because the underlying network event isn't a server lifecycle signal but a forged packet from a middlebox the user can't see or control. Users on flaky networks are going to keep hitting this regardless of any server-side fix.
2. Negative results on two of the recommended workaround env vars
In case other users see them in your workaround stack and assume they're helping:
BUN_CONFIG_HTTP_RETRY_COUNT=10— I set this and ran a full session withBUN_CONFIG_VERBOSE_FETCH=curlcapturing every fetch. The verbose log shows exactly one fetch attempt per request ID, even for the failing ones. The env var appears to only govern Bun's internal HTTP client (e.g.,bun install), not user-levelfetch(). So setting it doesn't add fetch-layer retries.CLAUDE_CODE_MAX_RETRIES=50— for the session-breaking ECONNRESET path I'm seeing, the SDK never logs an "attempt N/M" line. The retry path isn't being entered at all for this error class, so increasing the max doesn't matter. (Consistent with your Finding 3 — the CLI path doesn't have the retry treatment that background agents do.)The other env vars in your workaround stack (
CLAUDE_CODE_REMOTE_SEND_KEEPALIVES,BUN_CONFIG_HTTP_IDLE_TIMEOUT,CLAUDE_STREAM_IDLE_TIMEOUT_MS,NODE_OPTIONS=--dns-result-order=ipv4first) I haven't tested yet — going to try them next.3.
BUN_CONFIG_VERBOSE_FETCH=curlworks, with a security caveatFor anyone else trying to instrument this: the env var IS honored in the compiled binary, but the output goes to stderr, not stdout — easy to miss if you only redirect stdout. It produces full curl-style request output including
Authorization: Bearer sk-ant-...headers verbatim. Anyone capturing this output for sharing should pipe throughsedto redact tokens before they hit disk.4. Address family observations
I tried disabling IPv6 on the active network service as a workaround. It appeared to help on first capture, but a follow-up session with verified-zero IPv6 packets (
tcpdumpconfirmed) hit the same middlebox-RST fingerprint on the IPv4 path. So at least on my network, address family isn't the variable. Possibly relevant to anyone wondering whether--dns-result-order=ipv4firstwould help — it might not, depending on whether the middlebox sits on both paths.Also confirmed @brunos3d's observation that Bun appears to bypass
/etc/hostsfor some fetches — I saw IPv6 SYNs in tcpdump output despite havingapi.anthropic.compinned to an IPv4 in hosts.Net
Strong +1 on the proposed fixes, particularly Fix 1 (
SO_KEEPALIVE) and Fix 3 (CLI retry treatment). Happy to share full packet captures and debug traces privately with anyone on the team who wants them — they contain account info I'd rather not post publicly.Specific request IDs from my captures, for server-side log correlation if useful:
8a91f305-6768-4721-bf2f-4d7bf63c5313— 2026-05-24T21:34:02Z (IPv6 path)64655465-9cbb-4691-beea-8b87a80ed18d— 2026-05-24T22:39:14Z (IPv4 path)a4c16288-54be-4eaf-82a6-ee8cd97c55ae— 2026-05-24T23:11:09Zd2d83bb1-cc44-4bfe-89fa-d1e26e370b29— 2026-05-24T23:13:08Z<img width="1368" height="446" alt="Image" src="https://github.com/user-attachments/assets/c13c1c68-ec5d-4ba8-8a16-0a3fa801ee9a" />
This screenshot was taken during a real workflow session today, and it seems to reinforce the same hypothesis again.
The connection consistently fails around ~28 seconds with the exact same socket error every time:
“API Error: The socket connection was closed unexpectedly.”
The timing consistency is what stands out the most here. It really looks like some kind of ~30s connection/socket timeout happening somewhere between the CLI and the backend infrastructure.
I’m sharing this mostly as additional debugging evidence in case the Anthropic team is actively investigating the issue. The pattern seems too consistent to be random.
After some retries with the same prompt, it worked, but tokens were wasted.
I have also encountered this error very frequently:
Claude Code v2.1.168as below, so I need manually go on my current work frequently:● API Error: The socket connection was closed unexpectedly. For more information, passverbose: truein the second argument to fetch()There is a high probability of occurrence when performing tasks involving deep research or multiple function calls
I'm getting this error in both Claude CLI 2.1.170 and Claude App 1.11847.5 (9692f0) on
windowsplatform.Getting this error regularly on Claude cli 2.1.177 on MacOS for a couple weeks now. I am going to attempt the workaround script provided by brunos3d (thanks @brunos3d !) and will report back on my results
Reproducing on macOS Apple Silicon — IPv6 was one factor, but a second transport-independent cause remains
Environment
Symptom
The socket connection was closed unexpectedlyafter an idle window (~30 min, session left open) and during normal use. Session must be restarted (or resumed with--continue).Finding #4 confirmed. With
NODE_OPTIONS=--dns-result-order=ipv4firstset and verified,lsofshowed every Claude connection still using IPv6 — the flag is ignored by Bun's native DNS resolver:claude.ex IPv6 TCP [...]->[2607:6bc0::10]:443 (ESTABLISHED) # api.anthropic.com
claude.ex IPv6 TCP [...]->[2600:1901:0:3084::]:443 (ESTABLISHED)
No
keepalivetimer on any socket. TheDYLD_INSERT_LIBRARIESSO_KEEPALIVE shim does not work on Apple Silicon — SIP/hardened-runtime blocks injection into signed binaries, so the macOS workaround path is unavailable.Two superimposed causes observed:
networksetup -setv6off Wi-Fi, confirmed vialsof→ all connections now160.79.104.10:443) noticeably increased time-to-failure — idle sessions survive much longer. So the IPv6 path is one trigger.Workarounds applied (all active, none fully prevent it):
CLAUDE_CODE_REMOTE_SEND_KEEPALIVES=true,BUN_CONFIG_HTTP_IDLE_TIMEOUT=300,BUN_CONFIG_HTTP_RETRY_COUNT=3,CLAUDE_STREAM_IDLE_TIMEOUT_MS=120000,NODE_OPTIONS=--dns-result-order=ipv4first, macOS sysctlmssdflt=1440 / blackhole=0 / delayed_ack=0.For macOS the OS-level shim path is a dead end (SIP). The two fixes that would actually help here are application-level: Fix 2 (non-zero
heartbeat_interval_msdefault for CLI sessions) and Fix 4 (transparent retry on HTTP/2 GOAWAY). The latter is the only thing that addresses the residual IPv4 failures.The results are unclear. I have not seen the exact "socket closed unexpectedly" error again, so possibly this specific issue is suppressed. I have seen several similar errors, both with API dropped types of messages and also interruptions due to sleep, which may have been due to a caffeinate problem that surely confused the issue. I think it is worth trying on YOUR system though.
Update - since fixing my caffeinate problem I have had no issues with long runs of 6 or 7 hours Claude running unattended. Strong evidence the workarounds @brunos3d created are effective.
Naive question I expect, but why doesn't the CLI just open a new connection if the existing one has died?
"Coding is largely solved", they say.
1 year of terminal Flickring and this GitHub issue both prove otherwise. Amazingly, Anthropic can't make long-running tasks work in MacOS even tho they say "coding is largely solved". Ok, so can't you ask for Mythos to fix this? OMG.
After weeks of ponderation and wasted tokens, I just cancelled my Anthropic subscription. And when the company I work for recently asked me to choose between Anthropic and OpenAI, you know what the choice was. And all of the team members followed. $200 per team member going to OpenAI instead of Anthropic because of a stupid, very annoying bug that could be easily fixed if they even cared.
Been following this since filing #62146 back in May (closed by the stale-bot on 2026-06-28 for inactivity — not resolved, so consolidating the follow-up here rather than reopening a separate thread). Have two things that might help triage: real before/after data on the env-var workaround, and a new failure shape that doesn't show up in the single-session case this issue mostly discusses.
The env-var workaround actually works — here's the data
Saw
@mnrsiat's "the results are unclear" above. We ran a controlled measurement on the two most directly-applicable knobs and have a clean answer.Applied 2026-05-27/31:
(via
.claude/settings.local.json'senvblock — targets 2 of the 5 mechanisms from the original report: idle-timeout handling and retry count.)Socket-disconnect corpus, before/after (our own tracked incidents, not a public sample):
| Window | Range |
socket_closedcount ||---|---|---|
| Pre-apply baseline | 7 days, 2026-05-24 → 05-31 | 40 |
| Post-apply | first 3.7 days | 1 |
| Post-apply | 2026-06-05 → 06-12 | 4 (isolated, non-clustered) |
| Post-apply | 2026-06-12 → 2026-07-02 | 0 (20 days) |
That's a real, sustained effect — not noise, not a lucky week. For anyone still deciding whether to bother with these two env vars: it's worth it.
But it doesn't close the gap for concurrent multi-agent fan-out
Two incidents today (2026-07-02), both hitting the class this issue is about, both with the mitigation above confirmed active:
API Error: Connection closed mid-response (idleReason: failed). Respawned clean, no work lost.Status page was clean (All Systems Operational) through the whole window both times.
This tracks with what
@loseinworldreported back on #62146 in May: 4 parallel sub-agent calls in one turn → 2 of 4 hitConnectionRefused(50% failure rate), sequential issuing far more reliable. We adopted a conditional "go sequential if transport looks degraded" rule as a workaround, but that's reactive — it only helps after you've already eaten one failure.We confirmed the env-var mitigation does reach these worker processes (not obvious — worth stating precisely since it rules out a propagation gap as the explanation): stripped the three vars from a freshly-spawned child process's own environment before launch, and the CLI still reported them set, meaning it's re-reading
.claude/settings.local.jsonand reapplyingenvat each process's own startup, independent of what spawned it. So this isn't "the fix didn't reach the sub-processes" — it's "the fix helps the single-stream case a lot, and the transport failure still occurs under concurrent fan-out even when every process individually has the mitigation."Questions, if anyone's triaging
Connection closed mid-response (idleReason: failed)(current CLI, 2.1.198) the same underlying failure as the originalsocket connection was closed unexpectedlyraw fetch() error this issue documents, just with newer/more-structured harness-side labeling — or a distinct code path? Asking because the "idleReason" framing suggests a named health-check timeout rather than a raw transport exception, and I'd rather not assume they're the same bug.@mnrsiat's question above — why not open a new connection when the old one's confirmed dead, at least for the non-interactive worker-process case where there's no user watching a live stream to disrupt.)