Persistent ECONNRESET Errors on macOS Network Connections

Status Open
Maintainer reply None cached
Activity 53 comments · opened Aug 13, 2025

Bug Description
I keep getting the following errors, and it's causing connection errors and it's disconnecting tasks within Claude. This only happens on my Mac OS; it doesn't happen on my Windows server (which is on the same network) nor does it happen on my Linux server (which is on an OCI network). I've read online that this is an issue with Mac OS. I have been troubleshooting all day for over 10 hours, and I can't figure out what's going on. I believe this is a Claude issue, not a user issue. If it is a user issue, please guide me in regards to how to resolve this issue because I am stumped. I have been working on this for over 12 hours now, and that made zero progress.

These are the errors that I see: n, pass verbose: true in the second argument to fetch()) (ECONNRESET)
⎿ API Error (Connection error.) · Retrying in 32 seconds… (attempt 7/10)
⎿ Error (The socket connection was closed unexpectedly. For more information, pass verbose: true in the second argument to fetch()) (ECONNRESET)
⎿ API Error (Connection error.) · Retrying in 37 seconds… (attempt 8/10)
⎿ Error (The socket connection was closed unexpectedly. For more information, pass verbose: true in the second argument to fetch()) (ECONNRESET)
⎿ API Error (Connection error.) · Retrying in 33 seconds… (attempt 9/10)
⎿ Error (The socket connection was closed unexpectedly. For more information, pass verbose: true in the second argument to fetch()) (ECONNRESET)
⎿ API Error (Connection error.) · Retrying in 40 seconds… (attempt 10/10)
⎿ Error (The socket connection was closed unexpectedly. For more information, pass verbose: true in the second argument to fetch()) (ECONNRESET)
⎿  API Error: Connection error.

Environment Info

  • Platform: darwin
  • Terminal: Apple_Terminal
  • Version: 1.0.77
  • Feedback ID: 64d3964f-f289-4803-a28c-1fd74ede068e

Errors

[{"error":"Error\n    at <anonymous> (unknown)\n    at onData (node:child_process:153:110)\n    at emit (node:events:95:22)\n    at addChunk (internal:streams/readable:265:47)\n    at readableAddChunkPushByteMode (internal:streams/readable:243:18)\n    at handleNumberResult (internal:streams/native-readable:69:18)\n    at <anonymous> (internal:streams/native-readable:43:68)\n    at processTicksAndRejections (unknown:7:39)","timestamp":"2025-08-13T09:22:33.750Z"},{"error":"Error: Connection error.\n    at new eW (unknown:1:28)\n    at new qY (/$bunfs/root/claude:788:1853)\n    at new BM (/$bunfs/root/claude:788:2749)\n    at makeRequest (/$bunfs/root/claude:805:4449)\n    at processTicksAndRejections (native:7:39)","timestamp":"2025-08-13T09:32:49.094Z"}]

View original on GitHub ↗

53 Comments

github-actions[bot] · 1 year ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/5585
  2. https://github.com/anthropics/claude-code/issues/4297
  3. https://github.com/anthropics/claude-code/issues/3131

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

ryanmr82 · 1 year ago

I'm not sure if this is a duplicate because this is only occurring on macOS and some other people are seeing it on Windows. I think this requires further investigation.

yasserlens · 1 year ago

I'm getting connectivity issues across all apps (including Anthropic's Claude desktop app) and web requests (Chrome).
The issues go away when I shut down from Claude Code instances (running in a terminal inside Cursor)

I'm on an Apple M2 Pro
macOS
version: 15.6

MarceloCajueiro · 10 months ago

I'm having the same @yasserlens' error. Any thought?

yasserlens · 10 months ago

@MarceloCajueiro my issue is now resolved - it had nothing to do with the Claude app it seems. Turns out this problem I had was due to using a VPN (TunnelBear) which seemed to have had issues. Uninstalling and deleting it helped (although was a challenge - uninstalling it didn't remove all pieces of the software that interfered with the network so I had to do some stuff manually). All done with the help of ChatGPT/Claude :)

MarceloCajueiro · 10 months ago

Thanks for the light @yasserlens! Here is the diagnostic and probably solution.

🔍 Additional Root Cause: VPN Residue Causing ECONNRESET on macOS

I experienced similar ECONNRESET issues on macOS after uninstalling a VPN client. Through investigation with Claude Code, we discovered leftover VPN network configurations were interfering with socket connections.

Diagnostic Findings

System: macOS 15.0.0 (Darwin 25.0.0)

Symptoms:

  1. 6 active utun tunnel interfaces (normal: 2-3)
  2. Multiple conflicting IPv6 default routes:

``
default → fe80::%utun0
default → fe80::%utun1
default → fe80::%utun2
default → fe80::%utun3
default → fe80::%utun4
default → fe80::%utun5
``

  1. Network performance degraded: Latency spiked 196% (26ms → 77ms)
  2. Unstable routing: Traceroute showing timeouts and packet loss

How This Causes ECONNRESET

The multiple competing IPv6 routes cause routing conflicts where:

  • TCP connections start on one interface
  • Packets get routed through different utun interfaces mid-stream
  • Connection appears broken → socket reset → ECONNRESET

This explains why it's macOS-specific and intermittent (depends on which route the OS chooses).

Diagnostic Commands

# Check for excessive utun interfaces
ifconfig | grep -c "utun"

# Check for multiple IPv6 default routes
netstat -rn | grep "default.*utun"

# Test if VPN residue exists
scutil --nc list

If you see 4+ utun interfaces and multiple IPv6 default routes, VPN residue is likely interfering.

✅ Solution That Worked

Disable IPv6 on Wi-Fi:

sudo networksetup -setv6off Wi-Fi

Results:

  • ECONNRESET issues resolved
  • Latency improved 48% (77ms → 40ms)
  • Stable routing restored

To re-enable: sudo networksetup -setv6automatic Wi-Fi

Why This Happens

After VPN uninstallation, macOS system services (nesessionmanager, rapportd) don't properly clean up tunnel interfaces. Research from Apple Developer Forums (2024-2025) confirms:

  • utun interfaces cannot be manually destroyed (ifconfig destroy fails)
  • Deleting routes doesn't work (auto-recreated)
  • Only reboot temporarily clears them (they return)

Alternative Solutions

If you need IPv6:

  1. Disable Handoff/Continuity (reduces utun creation):
  • System Settings → General → AirDrop & Handoff
  • Uncheck "Allow Handoff between this Mac and your iCloud devices"
  1. Monitor and reboot periodically:

``bash
# Check utun count
netstat -rn | grep "default.*utun" | wc -l
# If >3, reboot
``

Feature Request

Could Claude Code detect VPN residue during connection errors and suggest network cleanup? Something like:

⚠️  Detected network configuration issues (6 utun interfaces, IPv6 routing conflicts)
   This may cause ECONNRESET errors. Run diagnostics? [Y/n]

References

---

This solution might help others experiencing ECONNRESET on macOS after using VPN software. The key indicator is multiple utun interfaces with competing routes.

github-actions[bot] · 8 months ago

This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.

dacort · 8 months ago

This turned out to also be a network configuration error for me, but specifically with my MTU setting (and possibly eero secure).

When looking at my primary wireless interface (en0), I had an mtu of 1500:

 ifconfig | grep mtu
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500

Setting my MTU to 1492 (I had tried just 1400 originally) made everything work fine again.

sudo networksetup -setMTU Wi-Fi 1492

One thing that pointed me _away_ from it just being a CC issue was that I also couldn't load https://docs.bsky.app or https://superuser.com with Chrome or Vivaldi (but Safari worked fine). They're all behind a CloudFlare CDN so I don't know if they're implementing some TLS 1.3 thing in a specific way or what.

brigleb · 7 months ago

OK after tons of troubleshooting, it turns out the presence of Tailscale on my Mac, which I forgot I had installed and which was periodically launching, was my problem. This was causing intermittent failures, only on certain network stacks, that was driving me insane. Wanted to post this here to help others who might have this issue. Check your network and VPN setup!!

Update: More specifically, Tailscale (I suspect) set my TCP MSS (max segment size) to 512. Insane. Should be 1460, and presumably many apps expect that value. As Claude reports:

A TCP MSS of 512 is absurdly low—it's fragmenting your packets into tiny pieces, which would absolutely cause issues with HTTP/2 streaming while basic HTTP requests (like curl) might still work.

You can temporarily fix this on your Mac with sudo sysctl -w net.inet.tcp.mssdflt=1460 but that does not persist after reboot. I found it very difficult to remove residual Tailscale daemons, they stayed even after it was completely removed from System Settings. But maybe by adjusting that setting you can use Claude Code to track it down, which is what I did!

andreafalzetti · 7 months ago

We are experiencing the same error, and none of the above tips have worked. Please help!

S0uffle · 7 months ago

The issue with me is the Tidal Exclusive Mode. I don't know much about technical, so here is what I found :

TIDAL (and some other apps) installs a DNS proxy on your Mac. This intercepts all DNS lookups (converting domain names like api.anthropic.com to IP addresses) and routes them through TIDAL's system — likely for:

Bypassing regional restrictions on music
Optimizing streaming performance
Analytics/tracking

Use Tidal without Exclusive Mode solve this with me.

linchun3 · 7 months ago

FWIW: This happened to me and after some debugging, it was Lulu/little snitch that was blocking claude 😄

lukehebb · 7 months ago

Facing same issue. Without using a VPN I get this error constantly. Began happening randomly one day last week

Can't submit /feedback as it fails with or without VPN

Claude desktop app and web works with and without VPN, issue just affects claude code

CaptainMcCrank · 6 months ago

# ECONNRESET on macOS Despite All Network Connectivity Verified — Claude Code Internal Connection Handling Bug

Environment

| Component | Detail |
|:-:|:-:|
| OS | macOS (Apple Silicon / arm64) |
| Node | v22.17.0 (via nvm) |
| Claude Code | 2.1.30 (tested both native Bun install and npm Node install — identical failure) |
| Auth | OAuth, Max subscription |
| Working comparison | Same account/credentials work fine on Linux (Ubuntu, Claude Code 2.1.29) on the same network |

Summary

Claude Code 2.1.30 fails with ECONNRESET on every API call on macOS. Extensive troubleshooting has confirmed the issue is internal to Claude Code's connection handling, not the network, runtime, SDK, or auth.

What Works (all from the same Mac)

  • curl -v https://api.anthropic.com — TLS handshake, HTTP/2, returns 404 ✅
  • node -e "fetch('https://api.anthropic.com')..." — returns 404 ✅
  • node with https.request POST to /v1/messages — returns 401 ✅
  • node with http2 module to api.anthropic.com — returns 401 ✅
  • @anthropic-ai/sdk direct test (npm install @anthropic-ai/sdk) — returns 401 ✅
  • DNS resolution for api.anthropic.com, platform.claude.com, statsig.anthropic.com — all resolve ✅

⠀What Fails

  • claude — ECONNRESET within ~2 seconds, retries 10 times, never connects ❌
  • claude login — same ECONNRESET ❌
  • Both native (Bun) and npm (Node) installs fail identically ❌

⠀Verified Not the Cause

  • No VPN, proxy, firewall, or network content filters
  • No macOS system extensions (systemextensionsctl list returns 0)
  • No managed profiles (profiles list returns none)
  • /etc/hosts is clean (only localhost entries)
  • No HTTP_PROXY / HTTPS_PROXY / NODE_* environment variables set
  • macOS Application Firewall is disabled
  • No kernel extensions loaded (kextstat clean)
  • Credentials file (.credentials.json) is present with valid OAuth token
  • Removing and reinstalling Claude Code did not help
  • Clearing Keychain entries did not help

⠀Debug Log Evidence

2026-02-03T21:56:26.297Z [DEBUG] [API:request] Creating client, ANTHROPIC_CUSTOM_HEADERS present: false, has Authorization header: false

2026-02-03T21:56:26.297Z [DEBUG] [API:auth] OAuth token check starting

2026-02-03T21:56:26.297Z [DEBUG] [API:auth] OAuth token check complete

...

2026-02-03T21:56:27.374Z [ERROR] Error: Error: Connection error.

at makeRequest (/$bunfs/root/claude:361:4548)

at processTicksAndRejections (native:7:39)

Network-Level Observations

The connection does establish to 160.79.104.10:443 (confirmed via lsof) but then immediately resets. The lsof output shows the TCP socket transitions from ESTABLISHED to CLOSED state shortly after connecting:

node 11075 patrick 22u IPv4 0x2dc13daa605e3e7 0t0 TCP 192.168.x.x:50972->160.79.104.10:443 (CLOSED)

Notably, marketplace refresh succeeds within the same process:

2026-02-03T21:56:30.997Z [DEBUG] Successfully refreshed marketplace: claude-plugins-official

This suggests only specific API request paths are affected.

Telemetry Also Fails with the Same Error

[ERROR] Error: Error: 1P event logging: 26 events failed to export

(code=ECONNRESET, The socket connection was closed unexpectedly.

For more information, pass verbose: true in the second argument to fetch())

Suspected Cause

Something in Claude Code's internal HTTP client configuration or request construction is triggering a connection reset that does not occur when using the same SDK, same runtime, same credentials, and same endpoints outside of Claude Code. The fact that the marketplace refresh succeeds while API calls and telemetry fail within the same process suggests the issue may be specific to how certain requests are constructed or routed internally.

Steps to Reproduce

  1. Install Claude Code 2.1.30 on macOS (Apple Silicon)
  2. Authenticate via OAuth (Max subscription)
  3. Run claude from any directory
  4. Observe ECONNRESET within ~2 seconds, retrying up to 10 times before failing
CaptainMcCrank · 6 months ago
# # ECONNRESET on macOS Despite All Network Connectivity Verified — Claude Code Internal Connection Handling Bug # Environment Component Detail OS macOS (Apple Silicon / arm64) Node v22.17.0 (via nvm) Claude Code 2.1.30 (tested both native Bun install and npm Node install — identical failure) Auth OAuth, Max subscription Working comparison Same account/credentials work fine on Linux (Ubuntu, Claude Code 2.1.29) on the same network # Summary Claude Code 2.1.30 fails with ECONNRESET on every API call on macOS. Extensive troubleshooting has confirmed the issue is internal to Claude Code's connection handling, not the network, runtime, SDK, or auth. # What Works (all from the same Mac) curl -v https://api.anthropic.com — TLS handshake, HTTP/2, returns 404 ✅ node -e "fetch('https://api.anthropic.com')..." — returns 404 ✅ node with https.request POST to /v1/messages — returns 401 ✅ node with http2 module to api.anthropic.com — returns 401 ✅ @anthropic-ai/sdk direct test (npm install @anthropic-ai/sdk) — returns 401 ✅ DNS resolution for api.anthropic.com, platform.claude.com, statsig.anthropic.com — all resolve ✅ ⠀What Fails claude — ECONNRESET within ~2 seconds, retries 10 times, never connects ❌ claude login — same ECONNRESET ❌ Both native (Bun) and npm (Node) installs fail identically ❌ ⠀Verified Not the Cause No VPN, proxy, firewall, or network content filters No macOS system extensions (systemextensionsctl list returns 0) No managed profiles (profiles list returns none) /etc/hosts is clean (only localhost entries) No HTTP_PROXY / HTTPS_PROXY / NODE_ environment variables set macOS Application Firewall is disabled No kernel extensions loaded (kextstat clean) Credentials file (.credentials.json) is present with valid OAuth token Removing and reinstalling Claude Code did not help Clearing Keychain entries did not help ⠀Debug Log Evidence ### 2026-02-03T21:56:26.297Z [DEBUG] [API:request] Creating client, ANTHROPIC_CUSTOM_HEADERS present: false, has Authorization header: false ### 2026-02-03T21:56:26.297Z [DEBUG] [API:auth] OAuth token check starting ### 2026-02-03T21:56:26.297Z [DEBUG] [API:auth] OAuth token check complete ### ... ### 2026-02-03T21:56:27.374Z [ERROR] Error: Error: Connection error. ### at makeRequest (/$bunfs/root/claude:361:4548) ### at processTicksAndRejections (native:7:39) # Network-Level Observations The connection does establish to 160.79.104.10:443 (confirmed via lsof) but then immediately resets. The lsof output shows the TCP socket transitions from ESTABLISHED to CLOSED state shortly after connecting: ### node 11075 patrick 22u IPv4 0x2dc13daa605e3e7 0t0 TCP 192.168.x.x:50972->160.79.104.10:443 (CLOSED) Notably, marketplace refresh succeeds within the same process: ### 2026-02-03T21:56:30.997Z [DEBUG] Successfully refreshed marketplace: claude-plugins-official This suggests only specific API request paths are affected. # Telemetry Also Fails with the Same Error ### [ERROR] Error: Error: 1P event logging: 26 events failed to export ### (code=ECONNRESET, The socket connection was closed unexpectedly. ### For more information, pass verbose: true in the second argument to fetch()) # Suspected Cause Something in Claude Code's internal HTTP client configuration or request construction is triggering a connection reset that does not occur when using the same SDK, same runtime, same credentials, and same endpoints outside of Claude Code. The fact that the marketplace refresh succeeds while API calls and telemetry fail within the same process suggests the issue may be specific to how certain requests are constructed or routed internally. # Steps to Reproduce 1. Install Claude Code 2.1.30 on macOS (Apple Silicon) 2. Authenticate via OAuth (Max subscription) 3. Run claude from any directory 4. Observe ECONNRESET within ~2 seconds, retrying up to 10 times before failing

I was able to resolve this through a system reboot of all things. No clarity on why this worked.

aaronepinto-multiply · 6 months ago
OK after tons of troubleshooting, it turns out the presence of Tailscale on my Mac, which I forgot I had installed and which was periodically launching, was my problem. This was causing intermittent failures, only on certain network stacks, that was driving me insane. Wanted to post this here to help others who might have this issue. Check your network and VPN setup!! Update: More specifically, Tailscale (I suspect) set my TCP MSS (max segment size) to 512. Insane. Should be 1460, and presumably many apps expect that value. As Claude reports: > A TCP MSS of 512 is absurdly low—it's fragmenting your packets into tiny pieces, which would absolutely cause issues with HTTP/2 streaming while basic HTTP requests (like curl) might still work. You can temporarily fix this on your Mac with sudo sysctl -w net.inet.tcp.mssdflt=1460 but that does not persist after reboot. I found it very difficult to remove residual Tailscale daemons, they stayed even after it was _completely_ removed from System Settings. But maybe by adjusting that setting you can use Claude Code to track it down, which is what I did!

Thank you, this fixed it for me!

MarceloCajueiro · 6 months ago

Update: Found a deeper root cause beyond VPN/sysctl — Node.js HTTP/1.1 connection exhaustion

I commented here before about VPN residue and utun interfaces. That was real but turned out to be only part of the problem. After months of dealing with this intermittently, I finally spent a full day doing systematic testing and found the actual root cause — and it's something that affects every Node.js app on macOS, not just Claude Code.

The real problem

Node.js fetch (via undici) uses HTTP/1.1 by default, which opens one TCP connection per request. CDNs like Cloudflare limit concurrent TCP connections per IP to roughly 50-150. When any Node.js process on your machine exceeds that threshold, the server starts refusing connections with ECONNRESET/ECONNREFUSED.

Here's the critical part that explains why your browser stops working too: Cloudflare uses shared IPs. When your Node.js app floods 160.79.104.10:443 (Anthropic's Cloudflare-fronted API) with hundreds of HTTP/1.1 connections, Cloudflare can temporarily block your IP for all domains behind that CDN — GitHub, NPM, Discord, and thousands of other sites suddenly become unreachable from your browser.

curl and browsers don't have this problem because they use HTTP/2 by default, which multiplexes all requests over a single TCP connection.

Proof

I ran a controlled test battery against external APIs:

| Method | 200 concurrent requests | 300 concurrent |
|---|---|---|
| fetch (HTTP/1.1) | 4 ok, 196 fail | worse |
| https.get (HTTP/1.1) | 3 ok, 197 fail | worse |
| http2 (native) | 200 ok, 0 fail | 300 ok, 0 fail |
| fetch + allowH2 | 200 ok, 0 fail | 300 ok, 0 fail |
| localhost (any method) | 1000 ok, 0 fail | — |

Localhost never fails because there's no per-IP connection limit. The problem is exclusively with remote servers.

Why this is particularly bad on macOS

macOS has some TCP defaults that make this worse:

  • tcp.mssdflt = 512 (dial-up era value — several people in this thread found this, including @brigleb with Tailscale)
  • tcp.blackhole = 1 (silently drops packets instead of sending RST)
  • tcp.msl = 2000 (TIME_WAIT only 4 seconds — connections get recycled too aggressively)

These sysctl values compound the HTTP/1.1 connection flood problem. VPNs like Tailscale, TunnelBear, and Cloudflare WARP can also set mssdflt to 512, which fragments packets and makes HTTP/2 streaming fail even when the protocol negotiation works.

How I'm solving it (3 layers)

Layer 1: Enable HTTP/2 globally for all Node.js processes

# Install undici in a shared location
mkdir -p ~/.node && cd ~/.node
npm init -y && npm install undici

# Create preload script: ~/.node/enable-h2.cjs
cat > ~/.node/enable-h2.cjs << 'SCRIPT'
try {
  const { setGlobalDispatcher, Agent } = require(
    require('path').join(require('os').homedir(), '.node/node_modules/undici')
  );
  setGlobalDispatcher(new Agent({ allowH2: true }));
} catch (e) {}
SCRIPT

# Add to ~/.zshrc (or ~/.bashrc)
echo 'export NODE_OPTIONS="--require $HOME/.node/enable-h2.cjs"' >> ~/.zshrc

This makes every Node.js process (Next.js, Inngest, scripts, tests) negotiate HTTP/2 automatically. If the server only supports HTTP/1.1, it falls back gracefully. Zero code changes required in any project.

Important limitation: this does NOT affect Claude Code itself (it's a Bun binary, not Node.js) or the AWS SDK (@aws-sdk/client-s3), which uses its own HTTP client.

Layer 2: Fix macOS TCP sysctl defaults

# Temporary (until reboot)
sudo sysctl -w net.inet.tcp.mssdflt=1440 net.inet.tcp.blackhole=0 \
  net.inet.tcp.msl=15000 net.inet.tcp.delayed_ack=0 net.inet.tcp.fastopen=3

For persistence, create /Library/LaunchDaemons/custom.sysctl.tuning.plist with those values.

| Parameter | macOS default | Fixed | Why |
|---|---|---|---|
| tcp.mssdflt | 512 | 1440 | 512 is dial-up era; 1440 is correct for MTU 1492 |
| tcp.blackhole | 1 | 0 | Was silently dropping packets instead of RST |
| tcp.msl | 2000 | 15000 | TIME_WAIT was 4s, way too aggressive |
| tcp.delayed_ack | 3 | 0 | Immediate ACK, less latency |

Layer 3: For apps using AWS SDK / S3Client (Cloudflare R2, S3, etc.)

The AWS SDK v3 uses node:https internally with no keepAlive by default — every PutObject, GetObject, etc. opens a new TCP connection. If your endpoint is Cloudflare R2 (*.r2.cloudflarestorage.com), this can trigger the same CDN-wide IP block:

import { NodeHttpHandler } from '@smithy/node-http-handler';
import { Agent as HttpsAgent } from 'node:https';

const s3Client = new S3Client({
  // ... your config ...
  maxAttempts: 5,
  requestHandler: new NodeHttpHandler({
    httpsAgent: new HttpsAgent({ keepAlive: true, maxSockets: 25 }),
    connectionTimeout: 10_000,
    socketTimeout: 120_000,
  }),
});

What does NOT work

| Approach | Result |
|---|---|
| dns.setDefaultResultOrder("ipv4first") | Makes it worse (1/100 success) — disables Happy Eyeballs RFC 8305 |
| Increasing http.globalAgent.maxSockets | Doesn't help — more sockets = more refusals from server |
| Semaphore/queue to limit concurrency | Palliative — complex and fragile, HTTP/2 solves it at the root |
| sysctl tuning alone | Doesn't solve it — helps marginally but root cause is HTTP/1.1 |

How this relates to other comments in this thread

  • @brigleb found tcp.mssdflt=512 from Tailscale — that's Layer 2 above. But even after fixing MSS, HTTP/1.1 connection floods can still trigger CDN blocks.
  • @dacort fixed their issue by setting MTU to 1492 — this is related but different. MTU affects packet fragmentation at the network level, while MSS affects TCP segment size. Both should be aligned.
  • @yasserlens and my previous comment about VPN residue / utun interfaces — VPNs often set mssdflt=512 AND create routing conflicts. Removing the VPN fixes both, but you can also fix MSS directly without removing the VPN.
  • @CaptainMcCrank saw connections ESTABLISHED then immediately CLOSED — this is consistent with CDN rate limiting. The TCP handshake succeeds but the CDN resets the connection when it detects too many concurrent connections from the same IP. Reboot fixed it because it cleared all accumulated TCP state.

Diagnostic one-liner

# Check if you're affected
sysctl net.inet.tcp.mssdflt net.inet.tcp.blackhole
# If mssdflt=512 or blackhole=1, you have the problem

For the Claude Code team

Claude Code itself (Bun binary) likely already uses HTTP/2 via Bun's fetch. But the subprocesses it spawns (MCP servers, tool calls, etc.) that use Node.js would be affected by HTTP/1.1 connection exhaustion. If Claude Code is making many concurrent API calls (parallel tool use, streaming + telemetry + marketplace refresh simultaneously), the HTTP/1.1 connections from those Node.js subprocesses could be triggering CDN rate limiting that then also affects the main Bun process.

Worth considering: should Claude Code set NODE_OPTIONS with allowH2 for spawned Node.js subprocesses by default?

mateuszkotarba · 6 months ago

Mac M1. For me claude worked on mobile hotspot but not on my home wifi. I struggle with that few weeks. I had to remove all network configurations by these commands:

sudo rm /Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist
sudo rm /Library/Preferences/SystemConfiguration/com.apple.network.identification.plist
sudo rm /Library/Preferences/SystemConfiguration/com.apple.wifi.message-tracer.plist
sudo rm /Library/Preferences/SystemConfiguration/NetworkInterfaces.plist
sudo rm /Library/Preferences/SystemConfiguration/preferences.plist

then reboot

Now it works. Maybe this will help someone.

martc03 · 6 months ago

Still experiencing this on macOS (Darwin 25.3.0) with Claude Code using Opus 4.6.

  • ECONNRESET errors on both Wi-Fi and ethernet connections
  • Short HTTP requests (curl) work fine — 10/10 succeed instantly (~0.2s)
  • Only Claude Code's long-lived streaming connections are affected
  • Retries eventually succeed but responses take 4x longer cycling through all retry attempts
  • Network is stable — problem is isolated to Claude Code
  • Confirmed not a firewall, DNS, proxy, or routing issue

Consistent with the HTTP/2 stale connection pooling theory from #23744.

MarceloCajueiro · 6 months ago

After 8 days after my post (https://github.com/anthropics/claude-code/issues/5674#issuecomment-3881326808) I can say properly that worked for me forcing to use HTTP/2.

woytekbode · 5 months ago

I have the same problem using Claude Cowork on windows.

abdullahghani99 · 5 months ago
OK after tons of troubleshooting, it turns out the presence of Tailscale on my Mac, which I forgot I had installed and which was periodically launching, was my problem. This was causing intermittent failures, only on certain network stacks, that was driving me insane. Wanted to post this here to help others who might have this issue. Check your network and VPN setup!! Update: More specifically, Tailscale (I suspect) set my TCP MSS (max segment size) to 512. Insane. Should be 1460, and presumably many apps expect that value. As Claude reports: > A TCP MSS of 512 is absurdly low—it's fragmenting your packets into tiny pieces, which would absolutely cause issues with HTTP/2 streaming while basic HTTP requests (like curl) might still work. You can temporarily fix this on your Mac with sudo sysctl -w net.inet.tcp.mssdflt=1460 but that does not persist after reboot. I found it very difficult to remove residual Tailscale daemons, they stayed even after it was _completely_ removed from System Settings. But maybe by adjusting that setting you can use Claude Code to track it down, which is what I did!

This really helped me, man. Thanks a lot. IOU

pacificsky · 5 months ago

This might be useful for debugging for some scenarios
https://gist.github.com/pacificsky/5a4e85aa98a9c88110dd534c819390f9

wozman · 5 months ago

I had exactly the same problem on a brand new freshly installed MacBook Pro, so no VPN cruft, etc etc.
claude -p "<prompt>" worked fine, but interactive mode gave me consistent ECONNRESET errors.
Dropping the MTU on my Mac from 1500 down to 1450 fixed it for me. Setting it back to anything higher instantly gave me ECONNRESETs again, switch it back down to 1450 fixed. This was 100% reproducible.
You may have to tweak the numbers depending on your network setup, perhaps down as far as 1436.

Tilotiti · 5 months ago

Fun fact ...

My project is using Symfony Proxy in order to run my app.

Claude decided to create a startup script, and created the json file .claude/settings.json :

{
  "env": {
    "http_proxy": "http://localhost:7080",
    "https_proxy": "http://localhost:7080"
  }
}

It launched the preview but the Symfony proxy was not running.

So all the Claude session requests, not only the project one, where going through this proxy that was not running because claude named the proxy environment variable the same as it seems to be using internally.

Starting the Symfony Proxy made it work.

I deleted the settings files and just run it manually. Working fine now.

l3ntes · 4 months ago

Same problem here, with more tan 60 times on a morning

sibelius · 4 months ago

this is also happening for me inside a linux container running inside proxmox

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

wyatt-halluminate · 4 months ago

Me as well, never used to get this, am getting it several times a day now.

sibelius · 4 months ago

This was related to a bug if IPv6 not resolving in my machine

ddeellttaa · 4 months ago
## Update: Found a deeper root cause beyond VPN/sysctl — Node.js HTTP/1.1 connection exhaustion I commented here before about VPN residue and utun interfaces. That was real but turned out to be only part of the problem. After months of dealing with this intermittently, I finally spent a full day doing systematic testing and found the actual root cause — and it's something that affects every Node.js app on macOS, not just Claude Code. ### The real problem Node.js fetch (via undici) uses HTTP/1.1 by default, which opens one TCP connection per request. CDNs like Cloudflare limit concurrent TCP connections per IP to roughly 50-150. When any Node.js process on your machine exceeds that threshold, the server starts refusing connections with ECONNRESET/ECONNREFUSED. Here's the critical part that explains why your browser stops working too: Cloudflare uses shared IPs. When your Node.js app floods 160.79.104.10:443 (Anthropic's Cloudflare-fronted API) with hundreds of HTTP/1.1 connections, Cloudflare can temporarily block your IP for all domains behind that CDN — GitHub, NPM, Discord, and thousands of other sites suddenly become unreachable from your browser. curl and browsers don't have this problem because they use HTTP/2 by default, which multiplexes all requests over a single TCP connection. ### Proof I ran a controlled test battery against external APIs: Method 200 concurrent requests 300 concurrent fetch (HTTP/1.1) 4 ok, 196 fail worse https.get (HTTP/1.1) 3 ok, 197 fail worse http2 (native) 200 ok, 0 fail 300 ok, 0 fail fetch + allowH2 200 ok, 0 fail 300 ok, 0 fail localhost (any method) 1000 ok, 0 fail — Localhost never fails because there's no per-IP connection limit. The problem is exclusively with remote servers. ### Why this is particularly bad on macOS macOS has some TCP defaults that make this worse: tcp.mssdflt = 512 (dial-up era value — several people in this thread found this, including @brigleb with Tailscale) tcp.blackhole = 1 (silently drops packets instead of sending RST) tcp.msl = 2000 (TIME_WAIT only 4 seconds — connections get recycled too aggressively) These sysctl values compound the HTTP/1.1 connection flood problem. VPNs like Tailscale, TunnelBear, and Cloudflare WARP can also set mssdflt to 512, which fragments packets and makes HTTP/2 streaming fail even when the protocol negotiation works. ### How I'm solving it (3 layers) Layer 1: Enable HTTP/2 globally for all Node.js processes # Install undici in a shared location mkdir -p ~/.node && cd ~/.node npm init -y && npm install undici # Create preload script: ~/.node/enable-h2.cjs cat > ~/.node/enable-h2.cjs << 'SCRIPT' try { const { setGlobalDispatcher, Agent } = require( require('path').join(require('os').homedir(), '.node/node_modules/undici') ); setGlobalDispatcher(new Agent({ allowH2: true })); } catch (e) {} SCRIPT # Add to ~/.zshrc (or ~/.bashrc) echo 'export NODE_OPTIONS="--require $HOME/.node/enable-h2.cjs"' >> ~/.zshrc This makes every Node.js process (Next.js, Inngest, scripts, tests) negotiate HTTP/2 automatically. If the server only supports HTTP/1.1, it falls back gracefully. Zero code changes required in any project. Important limitation: this does NOT affect Claude Code itself (it's a Bun binary, not Node.js) or the AWS SDK (@aws-sdk/client-s3), which uses its own HTTP client. Layer 2: Fix macOS TCP sysctl defaults # Temporary (until reboot) sudo sysctl -w net.inet.tcp.mssdflt=1440 net.inet.tcp.blackhole=0 \ net.inet.tcp.msl=15000 net.inet.tcp.delayed_ack=0 net.inet.tcp.fastopen=3 For persistence, create /Library/LaunchDaemons/custom.sysctl.tuning.plist with those values. Parameter macOS default Fixed Why tcp.mssdflt 512 1440 512 is dial-up era; 1440 is correct for MTU 1492 tcp.blackhole 1 0 Was silently dropping packets instead of RST tcp.msl 2000 15000 TIME_WAIT was 4s, way too aggressive tcp.delayed_ack 3 0 Immediate ACK, less latency Layer 3: For apps using AWS SDK / S3Client (Cloudflare R2, S3, etc.) The AWS SDK v3 uses node:https internally with no keepAlive by default — every PutObject, GetObject, etc. opens a new TCP connection. If your endpoint is Cloudflare R2 (*.r2.cloudflarestorage.com), this can trigger the same CDN-wide IP block: import { NodeHttpHandler } from '@smithy/node-http-handler'; import { Agent as HttpsAgent } from 'node:https'; const s3Client = new S3Client({ // ... your config ... maxAttempts: 5, requestHandler: new NodeHttpHandler({ httpsAgent: new HttpsAgent({ keepAlive: true, maxSockets: 25 }), connectionTimeout: 10_000, socketTimeout: 120_000, }), }); ### What does NOT work Approach Result dns.setDefaultResultOrder("ipv4first") Makes it worse (1/100 success) — disables Happy Eyeballs RFC 8305 Increasing http.globalAgent.maxSockets Doesn't help — more sockets = more refusals from server Semaphore/queue to limit concurrency Palliative — complex and fragile, HTTP/2 solves it at the root sysctl tuning alone Doesn't solve it — helps marginally but root cause is HTTP/1.1 ### How this relates to other comments in this thread @brigleb found tcp.mssdflt=512 from Tailscale — that's Layer 2 above. But even after fixing MSS, HTTP/1.1 connection floods can still trigger CDN blocks. @dacort fixed their issue by setting MTU to 1492 — this is related but different. MTU affects packet fragmentation at the network level, while MSS affects TCP segment size. Both should be aligned. @yasserlens and my previous comment about VPN residue / utun interfaces — VPNs often set mssdflt=512 AND create routing conflicts. Removing the VPN fixes both, but you can also fix MSS directly without removing the VPN. * @CaptainMcCrank saw connections ESTABLISHED then immediately CLOSED — this is consistent with CDN rate limiting. The TCP handshake succeeds but the CDN resets the connection when it detects too many concurrent connections from the same IP. Reboot fixed it because it cleared all accumulated TCP state. ### Diagnostic one-liner # Check if you're affected sysctl net.inet.tcp.mssdflt net.inet.tcp.blackhole # If mssdflt=512 or blackhole=1, you have the problem ### For the Claude Code team Claude Code itself (Bun binary) likely already uses HTTP/2 via Bun's fetch. But the subprocesses it spawns (MCP servers, tool calls, etc.) that use Node.js would be affected by HTTP/1.1 connection exhaustion. If Claude Code is making many concurrent API calls (parallel tool use, streaming + telemetry + marketplace refresh simultaneously), the HTTP/1.1 connections from those Node.js subprocesses could be triggering CDN rate limiting that then also affects the main Bun process. Worth considering: should Claude Code set NODE_OPTIONS with allowH2 for spawned Node.js subprocesses by default?

dude, you are absolute genius!

i11010520 · 4 months ago
### Diagnostic one-liner # Check if you're affected sysctl net.inet.tcp.mssdflt net.inet.tcp.blackhole # If mssdflt=512 or blackhole=1, you have the problem ### For the Claude Code team Claude Code itself (Bun binary) likely already uses HTTP/2 via Bun's fetch. But the subprocesses it spawns (MCP servers, tool calls, etc.) that use Node.js would be affected by HTTP/1.1 connection exhaustion. If Claude Code is making many concurrent API calls (parallel tool use, streaming + telemetry + marketplace refresh simultaneously), the HTTP/1.1 connections from those Node.js subprocesses could be triggering CDN rate limiting that then also affects the main Bun process. Worth considering: should Claude Code set NODE_OPTIONS with allowH2 for spawned Node.js subprocesses by default?

@MarceloCajueiro Thanks for your deep insight, but it seems not work for me.

Error

Whatever claude or ollama launch claude, it will prompt 'Unable to connect to API (ECONNREFUSED)' even for '/init'.

Verify: node.js fetch

[concurrent-fetch.js]

// Node built-in fetch)

const URL = 'https://example.com'; // change this
const CONCURRENCY = 500;

async function fetchOnce(i) {
  try {
    const res = await fetch(URL);
    const text = await res.text();
    console.log(`#${i} → ${res.status} (${text.length} bytes)`);
    return res.status;
  } catch (err) {
    console.error(`#${i} → ERROR`, err.message);
    return null;
  }
}

async function main() {
  console.time('200 requests');

  const tasks = Array.from({ length: CONCURRENCY }, (_, i) =>
    fetchOnce(i + 1)
  );

  const results = await Promise.all(tasks);

  console.timeEnd('200 requests');

  const success = results.filter(s => s === 200).length;
  console.log(`Success: ${success}/${CONCURRENCY}`);
}

main();

Then the testing result says that fetch works fine, not like what you said "4 ok, 196 fail".

Success: 200/200
Success: 500/500

Try Layer 1

mkdir -p ~/.node && cd ~/.node

cat > ~/.node/enable-h2.cjs << 'SCRIPT'
try {
  const { setGlobalDispatcher, Agent } = require(
    require('path').join(require('os').homedir(), '.node/node_modules/undici')
  );
  setGlobalDispatcher(new Agent({ allowH2: true }));
} catch (e) {}
SCRIPT

export NODE_OPTIONS="--require $HOME/.node/enable-h2.cjs"

Then claude or ollama launch claude still same error.

Try Layer 2

❯ sudo sysctl -w net.inet.tcp.mssdflt=1440 net.inet.tcp.blackhole=0 \
  net.inet.tcp.msl=15000 net.inet.tcp.delayed_ack=0 net.inet.tcp.fastopen=3
Password:
net.inet.tcp.mssdflt: 512 -> 1440
net.inet.tcp.blackhole: 0 -> 0
net.inet.tcp.msl: 15000 -> 15000
net.inet.tcp.delayed_ack: 3 -> 0
net.inet.tcp.fastopen: 3 -> 3

Then claude or ollama launch claude still same error.

What a pity.

SnowLukin · 4 months ago
@MarceloCajueiro Thanks for your deep insight, but it seems not work for me.

Same. Did all the recommended steps – Claude does few responses and then gets stuck. Gets unstuck after ide reboot but then gets stuck again. Pretty annoying problem.
jfyi: I have vpn on (can't test without it for my own reasons)

viniciusmorgado · 3 months ago

Any solution for this? I'm getting the same issue on an M4, and I never uninstalled any VPN service. The same doesn’t seem to happen on Linux or Windows on the same network. I need to send the same prompt multiple times just to get it to work.

YeKc1M · 3 months ago

Same issue happens in my mac m4 and wsl. I never uninstall any VPN service.
When I start some a little long and complex work, it will be stuck and get the error.

rjk-laconic · 3 months ago

i just started getting this issue today out of nowhere.

romancone · 3 months ago

This issue is highly visible in version 2.1.143

Claude 2.1.126 still works fine

SnowLukin · 3 months ago

I started using claude desktop app and never seen that problem ever since. Only happens in cursor for me

pangpond · 3 months ago

It's happen to me to after electric drop. I try many way but didn't resolve.
last shot I try to connect with Warp by Cloudflare...It's fix!!!

brunos3d · 3 months ago

Root cause identified — Bun does not set SO_KEEPALIVE on TCP sockets

Posting here as this is the most active thread. We did a deep binary-level investigation on Linux (Arch/Manjaro) and found the root cause. Full report in #60133.

What's happening

The claude binary is a standalone Bun 1.3.14 executable (confirmed via strings). Bun's fetch() does not set SO_KEEPALIVE on its TCP sockets. During agentic sessions, the HTTP/2 connection sits idle while tools execute locally. Without keepalive probes, the connection is silently dropped by the router, ISP, or Anthropic's server — and Bun only discovers this when it tries to reuse the connection for the next API call.

Confirmed via ss -tnop state established '( dport = :443 )':

# Every other app 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:
claude  → 160.79.104.10:443 (no timer)                  ← SO_KEEPALIVE NOT SET

Additionally, all application-level keepalives are explicitly disabled via GrowthBook feature flags in ~/.claude.json:

"heartbeat_interval_ms": 0,
"session_keepalive_interval_ms": 0,
"session_keepalive_interval_v2_ms": 0

Why Cloudflare Warp fixes it on macOS

Warp routes traffic through a VPN tunnel that enforces its own keepalive at the tunnel layer — effectively compensating for the missing SO_KEEPALIVE in Bun. That's the same reason it "just works" through Warp.

Workaround (Linux/macOS)

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 NODE_OPTIONS="--dns-result-order=ipv4first"

For a more reliable OS-level fix, build an LD_PRELOAD shim that forces SO_KEEPALIVE on every TCP socket:

cat > /tmp/ka.c << 'CSRC'
#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;
}
CSRC
mkdir -p ~/.local/lib
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:

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

Verified: After applying these fixes, all claude sockets show timer:(keepalive,Xs,0) and two consecutive 90-second idle windows survive without error.

Suggested fix for Anthropic

Set SO_KEEPALIVE on the API socket — this is a one-liner and would fix the majority of reports across all platforms:

socket.setKeepAlive(true, 30000);

Full analysis, evidence table, and binary findings: #60133

searchingtofind · 3 months ago

Brunos3d this is the way!

CamiloEspinoza · 2 months ago

Same ECONNRESET, but in our case the root cause was a network-layer Path-MTU black hole — not keepalive. Sharing because the fix is completely different, and the keepalive workarounds above wouldn't have helped us. (Also, heads-up for macOS readers: the LD_PRELOAD shim and sysctl net.ipv4.* snippets are Linux-only — they're no-ops on macOS, where the signed/hardened claude binary also ignores DYLD_INSERT_LIBRARIES.)

The tell: it wasn't only Claude Code. Chrome also failed with ERR_CONNECTION_RESET on specific sites (Shopify-hosted stores), while Safari and curl loaded them fine on the same network. A Claude/Bun bug can't explain Chrome breaking too — so we packet-captured it.

What tcpdump showed (every connection attempt, fully deterministic):

192.168.1.93 > 23.227.38.74.443: Flags [S]                          # SYN      ok
23.227.38.74 > 192.168.1.93:     Flags [S.]                         # SYN-ACK  ok
192.168.1.93 > 23.227.38.74.443: Flags [.],  seq 1:1449  len 1448   # ClientHello part 1  -> 1500-byte packet
192.168.1.93 > 23.227.38.74.443: Flags [P.], seq 1449:1828 len 379  # ClientHello part 2  -> small packet
23.227.38.74 > 192.168.1.93:     Flags [R]                          # RESET
23.227.38.74 > 192.168.1.93:     Flags [.], ack 1, sack {1449:1828} # "I only ever got the SMALL segment"

That sack {1449:1828} together with ack 1 is the smoking gun: the far end received the small segment but never the full-size 1500-byte one. The path silently drops 1500-byte packets and no ICMP "fragmentation needed" comes back, so PMTUD never engages → the handshake stalls and the connection is reset.

Why this maps onto the reports in this thread:

  • curl / Safari work → their TLS ClientHello is small and fits in a sub-1500 packet.
  • Chrome and claude (Bun → BoringSSL) fail → their ClientHello is large (>1448 B) and requires a full 1500-byte segment, which is exactly what gets dropped.
  • Disabling Kyber / ECH / QUIC in Chrome changed nothing → the hello is still >1448 B and still needs that big segment.
  • A VPN / tunnel / Cloudflare Warp "fixes" it → not (only) because of keepalive, but because the tunnel re-encapsulates traffic with a smaller inner MTU/MSS, so the inner segments fit under the path limit. Same mechanism as @brigleb's Tailscale setting MSS to 512, and it matches @dacort's MTU 1492 fix exactly.

Fix (macOS, persistent):

# find your service name first:  networksetup -listallnetworkservices
sudo networksetup -setMTU "Wi-Fi" 1400     # or 1492 if your path supports it (PPPoE/fiber)

After this, Claude Code stopped throwing ECONNRESET and the sites loaded in Chrome — with no tunnel at all.

30-second triage to tell the two failure modes apart:

  • curl https://api.anthropic.com/ + Safari are fine, but Chrome / Claude / Cursor reset on big-ClientHello sites → it's MTU/MSS → lower your MTU.
  • Everything works initially and only drops after an idle period while tools run → that's the SO_KEEPALIVE class @brunos3d described.

So there seem to be (at least) two distinct buckets getting merged here: (1) idle-connection drops (the keepalive story) and (2) large-ClientHello packets dying in a PMTU black hole / clamped MSS (ours, @dacort, @brigleb). socket.setKeepAlive(true, …) would address bucket 1 but not bucket 2.

Suggestion for the team: on ECONNRESET, printing a hint like "if Chrome also fails on some sites but Safari/curl don't, try lowering your MTU (networksetup -setMTU …)" would have saved us a long debugging session.

searchingtofind · 2 months ago

Honestly... I'm not sure who else's has tried this.. I reset my computer. That was it. The econnrest disappeared. The instances were old.. they had been massive sessions with a lot of context and left open for weeks. I always save my sessions this way, before creating handoff prompts. Resetting my entire computer, often I leave it in sleep, or if I have an automated task, it runs silently all night. Nothing anyone suggested here worked.. until I just ended all instances and reset my computer

nikspyratos · 2 months ago

I've been having this in "longer-lived" sessions where "longer-lived" is something like 1h+, regardless of context size. Stopping and resuming the session doesn't work, occasionally I can get it to succeed in writing a handoff doc for a new session to continue. So it's not just "week long massive sessions", as I restart my CC frequently. Not having similar issues in other browsers

/feedback doesn't work either in the sessions, so you can't even report the bug within the session! Same ECONNRESET.

Hopefully #60133 is the real issue, but damn this has been very frequent as of this week for me, it's hard to get the large work CC is useful for done.

Seems tons of similar issues since last year already:
https://github.com/anthropics/claude-code/issues/66758
https://github.com/anthropics/claude-code/issues/56017
https://github.com/anthropics/claude-code/issues/28557
https://github.com/anthropics/claude-code/issues/66090
https://github.com/anthropics/claude-code/issues/64453
https://github.com/anthropics/claude-code/issues/60441
https://github.com/anthropics/claude-code/issues/65028
https://github.com/anthropics/claude-code/issues/62045
https://github.com/anthropics/claude-code/issues/62263
https://github.com/anthropics/claude-code/issues/56059
https://github.com/anthropics/claude-code/issues/62327
https://github.com/anthropics/claude-code/issues/66127
https://github.com/anthropics/claude-code/issues/41242
https://github.com/anthropics/claude-code/issues/62971
https://github.com/anthropics/claude-code/issues/57222
https://github.com/anthropics/claude-code/issues/51164
https://github.com/anthropics/claude-code/issues/63559

mmisztal1980 · 2 months ago

I've just run into this issue today, and made progress with Fable 5, here's what we've found:

ECONNRESET on large payloads — root cause: IPv6 PMTUD black hole (postmortem)

Environment: macOS (Apple Silicon), Claude Code CLI + VS Code extension anthropic.claude-code 2.1.170, home network (Polish ISP, IPv6 prefix 2a02:...), Ethernet via Dell Universal Dock D6000 (en7), no VPN.

TL;DR: Persistent ECONNRESET errors affecting large requests only (/compact, large document reviews via subagents) were caused by a Path MTU Discovery black hole on the ISP's IPv6 path, not by Claude Code or the local TCP stack. Small requests succeeded; any request with a large upload silently stalled mid-transfer and eventually reset. Fix: disable IPv6 on the active network interface. A separate, unrelated issue — Opus retry loops that also surfaced as ECONNRESET — was server-side load shedding during the June 8–10 incident window (confirmed via TCP state: server-initiated FIN after a clean 746 KB upload) and required no local action. Section 6 has a one-command discriminator table for telling the two apart.

---

Symptoms

  1. ECONNRESET errors in VS Code extension tabs, "all the time" — but intermittent in terminal sessions.
  2. /compact on a long-running (4.5h) session failed repeatedly:

``
⎿ Error: Error during compaction: API Error: Unable to connect to API (ECONNRESET)
``
One attempt took ~15 minutes to fail, stuck at 95% for the last ~10 minutes. Reproducible across models (failed identically on Sonnet) and across fresh processes.

  1. A subagent task reviewing two documents (~1,400 lines total) hung silently for 7+ minutes with the token counter frozen — no error, no progress.
  2. Long-running Opus job: 4h24m elapsed, only 27.3k tokens — i.e. spent the time in retry/backoff loops, not inference.
  3. Onset correlated with the Claude Fable 5 release window (~June 9–10, 2026).

Key distinguishing pattern

Small chatty requests work; large uploads hang silently, then reset.

This is the signature that separates a PMTUD black hole from generic connectivity problems, server outages, or connection-pool exhaustion. If everything fails, it's not this. If only big payloads fail, check the items below.

---

What to check, how, and what to expect

1. Which protocol and interface your traffic is actually using

pgrep -fl claude                 # find the claude PID (beware: matches Claude.app helpers too)
nettop -p <PID> -L 2             # two samples of per-connection byte counters

What we found (the smoking gun):

tcp6 2a02:a310:...:b710.54592<->2607:6bc0::10.443,en7,Established,
     bytes_in=12828, bytes_out=857613, rx_dupe=27, ...
# counters IDENTICAL across both samples → zero forward progress

What to read from it:

| Observation | Meaning |
|---|---|
| tcp6 to 2607:6bc0::10 | Traffic is on IPv6 — IPv4-targeted fixes (sysctl net.inet.tcp.mssdflt, netstat greps for 160.79.x) don't apply |
| Interface en7 (dock), not en0 (Wi-Fi) | MTU fixes applied to Wi-Fi were applied to the wrong interface |
| ~857 KB out vs ~13 KB in, counters frozen, dupes/retransmits | Large upload stalled mid-flight — classic PMTUD black hole: oversized segments dropped, ICMPv6 Packet Too Big never arrives, TCP retransmits into the void forever |

Pitfall we hit: the connection census netstat -an | grep "160.79.10" came back nearly empty and looked "clean" — because it only matched IPv4. Always check both stacks:

netstat -an | grep -E "160\.79\.|2607:6bc0" 

2. The mechanism (why large payloads specifically)

IPv6 routers never fragment. If a TCP segment exceeds any hop's MTU, that hop must drop it and send ICMPv6 Packet Too Big back so the sender shrinks its segments. If a middlebox filters those ICMPv6 messages (common on DS-Lite / tunneled IPv6 deployments), the sender never learns — full-size segments vanish silently, retransmits vanish too, the connection freezes, and after several minutes one side gives up → ECONNRESET.

  • Small requests (chat turns, tool results) fit under the real path MTU → fine.
  • /compact (ships the entire transcript in one POST) and large file reads → guaranteed full-size segments → guaranteed stall.
  • An interface MTU of 1450 did not help, because the true path MTU was lower still and PMTUD couldn't communicate it.

3. The fix

Identify the network service for the active interface and disable IPv6 on it:

networksetup -listallhardwareports          # map device (en7) → service name
sudo networksetup -setv6off "Dell Universal Dock D6000"
networksetup -getinfo "Dell Universal Dock D6000"   # verify: "IPv6: Off"

This setting is stored in the network service configuration and persists across reboots — no LaunchDaemon needed. Revert later with -setv6automatic.

Result: the identical subagent task that had hung for 7+ minutes with a frozen counter completed normally immediately after the switch (visible token streaming, multi-tool-use review finished). tcp4 connections to 160.79.x took over.

4. Optional: keep IPv6 by finding the true path MTU

# binary-search with don't-fragment pings against the API's v6 address
ping6 -D -s 1400 2607:6bc0::10     # fails → path MTU below ~1448
ping6 -D -s 1232 2607:6bc0::10     # should succeed (1280 minimum + headers)

Walk the size to the cliff, set the interface MTU to cliff + 48 via networksetup -setMTU. If moderate sizes black-hole with -D, the ISP path is eating ICMPv6 PTB (an RFC 4890 violation worth reporting to them); leave v6 off until fixed.

5. What did NOT fix it (tried first, ruled out)

| Attempted fix | Outcome | Why it couldn't work |
|---|---|---|
| NODE_OPTIONS undici allowH2 preload (from #5674) | No effect | Claude Code is a Bun binary — the Node preload never loads in the main process; also GUI-launched VS Code doesn't inherit shell env |
| sysctl net.inet.tcp.mssdflt=1440 etc. | No effect on this bug | Tunes the IPv4 stack; traffic was on IPv6 (net.inet6.tcp6.mssdflt is the v6 knob). Also sysctl -w is runtime-only — persists only via a LaunchDaemon plist |
| MTU 1492 on Wi-Fi | Seemed to help, didn't | Traffic was on en7 (dock), not en0 |
| pkill + fresh session / --resume | Recovered the session, not the bug | Fresh process, same broken v6 path |
| Switching models (Opus → Sonnet) | No effect on the resets | Network-layer problem, model-agnostic |

6. The unrelated second issue: Opus-side ECONNRESET (server load shedding)

Important caveat for anyone reading this issue: not every ECONNRESET is a network problem. After the network fix, the same review task succeeded under Sonnet but failed under Opus 4.7 (1M) — first cycling Retrying in 0s · attempt 5/10 (fast overload-class rejections), later escalating to Unable to connect to API (ECONNRESET) · Retrying in 11s · attempt 10/10. Same machine, same path, same payload; only the model differed. A network fault can never be model-dependent.

A nettop sample taken during the Opus failure proved the path was healthy and the close came from the server:

tcp4 192.168.0.122:62803<->160.79.104.10:443, en7, Established,  5201 in /  13299 out, 0 dupe, 0 re-tx, rtt 1ms
tcp4 192.168.0.122:64414<->160.79.104.10:443, en7, LastAck,      5228 in / 746526 out, 0 dupe, 0 re-tx, rtt 1ms
  • All connections tcp4 to 160.79.104.10 — the v6 fix held.
  • Zero retransmits/dupes, 1 ms RTT — clean transport.
  • The big request: 746 KB uploaded successfully, connection in LastAck — meaning the remote side sent FIN first. The server received the full payload and closed the connection instead of answering. That server-initiated close surfaces to the client as ECONNRESET.

This matched the elevated-error incidents on Opus 4.6/4.7/4.8 logged on status.claude.com around June 8–9 (Fable 5 launch window). No local action exists; use Sonnet or retry later.

One-command discriminator (nettop -p <claude_pid> -L 2, compare the two samples):

| Signal | PMTUD black hole (local/ISP path) | Server-side load shed |
|---|---|---|
| Byte counters across samples | frozen (no forward progress) | n/a — connection already closed |
| rx_dupe / re-tx | high (we saw 27 dupes) | zero |
| TCP state | Established but wedged | LastAck / CloseWait (remote FIN) |
| Model-dependent | no — hits all models equally | yes — one model fails, another works |
| Error appearance | silent multi-minute freeze, late reset | explicit retry attempts with backoff |

7. Why it "suddenly started"

The PMTUD black hole was a latent condition on the ISP's v6 path. The likely trigger (~June 9–10): client traffic to api.anthropic.com shifted to IPv6 — plausibly via Happy Eyeballs preferring a changed endpoint around the Fable release — moving bulk uploads onto the broken path for the first time. Cannot be proven after the fact; ISP-side routing changes are an equally valid trigger.

---

Suggestion for the Claude Code team

The client retried up to 11 attempts through a black-holed connection with a frozen spinner and surfaced only a generic ECONNRESET — this cost roughly a working day of local-stack debugging. Two cheap improvements:

  1. Detect mid-transfer stalls (bytes acknowledged stopped advancing during a large upload) and surface "upload stalled — possible path MTU issue" instead of a generic connection error.
  2. Log the protocol/address family and local interface for failing connections (tcp6 via en7 vs tcp4 via en0) — that single line in the error output would have shortcut this entire investigation.
  3. Distinguish reset origins in the error message. A server-initiated FIN/RST after a completed upload (load shedding) and a transport-level stall both surface as the same ECONNRESET string. The client has enough socket-level information to say "server closed the connection after receiving the request" vs. "connection stalled mid-upload" — these point to opposite ends of the wire and completely different remediations.
mmisztal1980 · 2 months ago

ECONNRESET follow-up — June 11: two distinct findings (IPv6 leak via second interface + server-side drops of large requests on Sonnet 4.6)

Environment: macOS (Apple Silicon), Claude Code CLI, home network (Polish ISP), Ethernet via Dell sUniversal Dock D6000 (en7), Wi-Fi (en0) up simultaneously. Follow-up to the June 9–10 postmortem (IPv6 PMTUD black hole, fixed by disabling IPv6 on the dock interface).

TL;DR: Two separate findings today.

  1. Local (fixed): Disabling IPv6 on the active interface was insufficient on a multi-homed machine — Wi-Fi was up alongside the dock with IPv6 still enabled, keeping a global v6 default route alive. Dual-stack connections could intermittently leak onto the broken ISP v6 path. Fix: disable IPv6 on every up interface; verify with ifconfig | grep -c "inet6.*2a02" → 0.
  2. Server-side (not fixable locally): With transport confirmed clean (all tcp4, 0 retransmits, 1 ms RTT), a Sonnet 4.6 session with ~680 KB request payloads had the same request dropped ~16 consecutive times — full upload accepted, server closes, no HTTP response — over ~25 minutes, with one partial success (719 tokens) that then stalled terminally. ~11 MB cumulative upload to deliver 719 tokens. No corresponding incident on status.claude.com. Mitigation: abandon the session; start fresh with a small seeded context; keep per-turn payloads small.

---

Finding 1: the IPv6 leak (multi-homed machine trap)

Symptom

A single ECONNRESET on Sonnet 4.6 (~14:17 CET) on a machine where the v6 black-hole fix had been applied and verified the previous day. Intermittent rather than reproducible — characteristically different from the constant failures of June 9–10.

Diagnosis

networksetup -getinfo "Dell Universal Dock D6000" | grep IPv6   # → IPv6: Off  (yesterday's fix held)
ifconfig | grep -c "inet6.*2a02"                                # → 2  ← global v6 addresses still active!
netstat -rn | grep "^default"

The routing table showed the leak:

default   192.168.0.1                       UGScg    en7      ← v4 via dock (fine)
default   192.168.0.1                       UGScIg   en0      ← v4 via Wi-Fi (fine)
default   fe80::ae22:5ff:fe1b:d4bc%en0      UGcIg    en0      ← **v6 default via Wi-Fi**

Wi-Fi was up alongside the dock with IPv6 enabled, advertising a v6 default route. Any dual-stack connection attempt (Happy Eyeballs) could pick v6 and land on the same broken PMTUD path. The intermittency follows: only connections that happened to choose v6 failed — frequency proportional to traffic, not deterministic.

Fix and verification

sudo networksetup -setv6off "Wi-Fi"          # persists in service config
ifconfig | grep -c "inet6.*2a02"             # → 0  (no global v6 addresses)
netstat -rn -f inet6 | grep "^default"       # → only fe80::%utunN link-local noise, no physical interface

Lesson

On a multi-homed machine, disabling IPv6 per-interface must cover every up interface, not just the active one. Verify at the address/route level (ifconfig, netstat -rn -f inet6), not at the service-config level. The remaining fe80::%utun* default routes are link-local only and cannot carry internet traffic.

---

Finding 2: server-side drops of large requests (Sonnet 4.6, no status-page incident)

Symptom

After the leak was closed (transport verifiably clean), an active session kept failing/retrying. Token counter advanced to 719 and froze.

Evidence — nettop -p <pid> -L 2 during the failure window (~15:22 CET)

Process totals: ~10.1 MB out / ~314 KB in. Connection table (abridged; all tcp4 on en7, all 0 re-tx / 0 dupe / 1 ms RTT):

tcp4 ...:59006 <-> 160.79.104.10:443  Established   7401 in /  547476 out   ← live attempt
tcp4 ...:50062 <-> 160.79.104.10:443  Closing      11259 in /  681573 out   ┐
tcp4 ...:63813 <-> 160.79.104.10:443  Closing      10856 in /  674514 out   │
tcp4 ...:53744 <-> 160.79.104.10:443  Closing      10041 in /  688633 out   │
tcp4 ...:59452 <-> 160.79.104.10:443  Closing      12281 in /  675396 out   │ ~15 connections,
tcp4 ...:55668 <-> 160.79.104.10:443  Closing      10259 in /  672257 out   │ each ~600–710 KB
tcp4 ...:56539 <-> 160.79.104.10:443  Closing       9082 in /  712595 out   │ uploaded, ~10 KB
...                                                                          │ received, closed
tcp4 ...:53529 <-> 160.79.104.10:443  LastAck      10604 in /  466627 out   ┘ by remote (FIN)

Interpretation:

  • Transport is perfect — zero retransmits, zero dupes, 1 ms RTT, all IPv4. This is not the network.
  • ~15 connections each carrying a complete ~680 KB upload, then closed by the server (Closing/LastAck = remote-initiated teardown) without an HTTP response — a proper 4xx/5xx would surface as an API error, not ECONNRESET. The client retried the identical request ~16 times; cumulative upload ≈ 16 × 680 KB ≈ 11 MB.
  • One attempt partially succeeded (719 tokens streamed), then stalled.

Confirming the stall — spaced sampling

nettop -L 2 samples ~300 ms apart — too short to judge a slow stream. Use spaced samples:

nettop -p <pid> -L 3 -s 10     # three samples, 10 s apart

Result: port 59006 frozen at 547,476 out / 7,401 in across all three samples (20 s) — zero bytes moved process-wide. Upload complete, server silent, connection wedged. Verdict: abandoned.

Why the payload was ~680 KB per turn

The session had previously read two design documents (~1,400 lines) plus subagent results into context. Claude Code re-sends accumulated context on every turn, so the session's own history became a ~680 KB per-message toll. On a healthy edge this is invisible; on a degraded edge, context size converts directly into failure probability — each turn is another large-request lottery ticket.

Mitigation

  1. Stop the retry loop — after the same bytes are rejected N times, attempt N+1 has no new information. Esc/kill.
  2. Do not /compact a struggling large session — compaction is itself one of the largest requests the client makes; it has worse odds than the failing turns.
  3. Fresh session, seeded small — a one-paragraph task summary + file references. The first turn of a fresh session is tiny and gets through.
  4. Operationally: compact early. Compact at natural checkpoints while the transcript is small, not heroically at hour four. On days with edge instability, keep per-turn payloads lean (work from diffs, not full re-reads).

Status-page correlation: none

No incident was logged for Sonnet 4.6 in the failure window (June 11, ~14:00–15:30 CET). The most recent logged incident was Haiku 4.5 on June 10. This failure mode — ~6% success rate for large requests with clean transport — was apparently below Anthropic's alerting threshold, which is itself a useful data point for the report.

---

Updated discriminator table (supersedes the June 10 version)

One command — nettop -p <claude_pid> -L 3 -s 10 — separates three ECONNRESET origins:

| Signal | PMTUD black hole (ISP path) | Server load shed / overload | Server drops large requests (today) |
|---|---|---|---|
| Protocol/route | tcp6, or v6 default route present | tcp4, clean | tcp4, clean |
| re-tx / rx_dupe | high (retransmitting into void) | zero | zero |
| Byte counters | frozen mid-upload | n/a (closed fast) | full upload out, ~10 KB in, then frozen/closed |
| TCP states | Established, wedged | LastAck/CloseWait | many Closing/LastAck, one ~identical large upload each |
| Repetition pattern | every large request, deterministic | bursty, model-dependent | same request rejected serially, rare partial success |
| Model-dependent | no | yes | partially (whichever pool's edge is degraded) |
| Status page | n/a (local) | usually has an incident | may have none |
| Fix | disable v6 / fix path MTU on all interfaces | wait / switch model | abandon session, fresh small context |

---

Additional suggestion for the Claude Code team (beyond the June 10 list)

  1. Cap identical-request retries on connection-level failures. The client re-uploaded an identical ~680 KB body ~16 times within ~25 minutes after server-initiated closes. When the same payload is killed repeatedly post-upload, later attempts add cost without information. Detecting "uploaded fully, closed without response, N consecutive times" and surfacing "the server is repeatedly dropping this request — consider /compact in a fresh process or reducing context" would convert a silent 25-minute failure into an actionable 1-minute one.
7rhh7v89hb-rgb · 1 month ago

I think the solution is easier than we thought.

On macOS, we have the private relay always on. For some reason, it stops Claude Code. So I’ve stopped it for one day an Claude started to work straight away

paulosantos-ios · 1 month ago

Two forensic captures at the exact moment of attempt 10/10 ECONNRESET — transport verifiably clean, failure is session/payload-dependent (supports the "server drops large requests" bucket)

Environment: macOS (Apple Silicon, Darwin 25.4.0), Claude Code 2.1.201, Max plan, Wi-Fi 802.11ax (SNR 54 dB, 2.1 Gbps PHY rate), no VPN, no global IPv6 addresses (all traffic tcp4).

I set up a LaunchAgent that samples every claude process's connections every 20s (netstat + nettop per PID) and fires a deep diagnostic bundle (spaced nettop, gateway ping, 1500B don't-fragment MTU probe, fresh TLS handshake timing, DNS, status page) the moment an API error entry lands in a session transcript. That gives measurements from the exact second of failure, not after-the-fact.

Ruled out on this machine before capturing (following the buckets earlier in this thread):

  • IPv6 PMTUD black hole: no global v6 addresses at all
  • MTU black hole: 1500-byte DF pings to api.anthropic.com, 0% loss
  • VPN residue: utun routes are link-local only
  • Wi-Fi signal: SNR 54 dB
  • Idle-drop / SO_KEEPALIVE class: applied sudo sysctl -w net.inet.tcp.always_keepalive=1 net.inet.tcp.keepidle=60000 (the macOS-native equivalent of the Linux LD_PRELOAD shim posted above — no shim needed on macOS). Verified working: idle connections now survive long local tool execution. The failures below happened anyway, so keepalive is not the remaining cause on this machine.

Capture 1 (11:55 local) and capture 2 (12:01 local), both during a live Unable to connect to API (ECONNRESET) · attempt 10/10:

| Signal | Capture 1 | Capture 2 |
|---|---|---|
| Gateway RTT (local hop) | 2.75–3.02 ms, 0% loss | 2.60–3.62 ms, 0% loss |
| api.anthropic.com ICMP | avg 19 ms, 0% loss | avg 18 ms, 0% loss |
| Fresh TLS handshake to api.anthropic.com | 68 ms total (3/3 succeeded, 63–128 ms) | 83 ms total |
| 1500B DF MTU probe | pass, 0% loss | pass, 0% loss |
| status.claude.com | All Systems Operational | All Systems Operational |

Meanwhile, in the same seconds:

  • The failing session's transcript is 19.4 MB and has accumulated 15 API error entries.
  • Other Claude Code sessions on the same machine kept talking to the API normally. Same host, same route, same second — one session fails 10/10, the others work. A network fault can't select by session; payload size can.
  • The failing request's connection showed ~984 KB uploaded, then counters frozen (upload complete, no response), followed by a retry burst of ~26 new connections on sequential ports (55374–55399) — the client re-sending the same large body.

This matches @mmisztal1980's "server drops large requests" discriminator exactly: tcp4, clean transport, zero retransmit growth at failure time, full upload out, closed without an HTTP response, same request rejected serially, no status-page incident.

Repeating the earlier asks, which this data supports:

  1. Cap identical-payload retries — 10 attempts of the same ~1 MB body add cost without information once the server has dropped it repeatedly.
  2. Distinguish reset origin in the error message — "server closed the connection after receiving the request" vs "connection stalled mid-upload" point to opposite ends of the wire.
  3. Server-side correlation — this failure mode (large bodies dropped with green status page) appears to be below the alerting threshold; edge logs for large-request drops would confirm.

Happy to share the full incident logs (sampler + diagnostics) if useful. Filed via /feedback as well.

Update: filed via /feedback from the affected session — Feedback ID: 42bef73e-e462-4574-90a3-699ce52b2a77 (the transcript attached there is the failing 19.4 MB session). Notably, the /feedback submission itself went through on the first attempt from that same session — consistent with small requests passing while large ones get dropped.

Update 2 (same day, ~5h later): two more captures with the identical signature — but on a different session (13.7 MB transcript this time): gateway 2.8–3.5 ms / 0% loss, api.anthropic.com ICMP normal, fresh TLS fine, status page green, while that session's requests died with ECONNRESET. Live observation during one failure: ~95 KB request body uploaded, then both byte counters frozen (upload complete, no response) until reset. So this is not one pathological session — any large-transcript session reproduces it, and the failing body doesn't need to be huge (~95 KB–1 MB observed). Four captures, two sessions, one signature: server-side/edge drops selecting on request size, invisible on the status page.

viniciusmorgado · 1 month ago
I think the solution is easier than we thought. On macOS, we have the private relay always on. For some reason, it stops Claude Code. So I’ve stopped it for one day an Claude started to work straight away

This is not a solution. Claude should work even with Private Relay enabled, since it is an OS default setting.

jbwhiky · 1 month ago

I’ve also encountered this error repeatedly. I think I’ve finally managed to resolve it, though it’s an issue that probably shouldn't be happening in your case. It turns out I was working with specifications, and a piece of text in one of the documents was being blocked by the company firewall, which was triggering that ECONNRESET error. I identified the problem by asking the AI ​​to analyze the entire process to pinpoint the connection failure—which theoretically shouldn't have existed—and within a few minutes, it managed to detect, isolate, and resolve it.

HlaKarki · 1 month ago

I have also been hitting intermittent ECONNRESET errors during long Claude Code sessions on macOS. After looking through my local session data, my working hypothesis is that large sessions make these failures more likely.

I do not think this proves a single root cause. I am sharing the numbers because they may help distinguish large-request failures from the idle-connection problem discussed elsewhere in this thread.

Environment

  • Claude Code 2.1.218, native macOS arm64 build
  • Bundled Bun 1.4.0
  • macOS 26.5.2
  • No configured VPN services or proxy environment variables
  • Active Wi-Fi interface at MTU 1500

Observations

My affected session had an 8.6 MB transcript. Immediately before its three ECONNRESET failures, the API usage records showed roughly 343k to 366k input and cache-read tokens. Each failed request returned zero tokens.

Two of the failures appeared about three minutes after a tool result, while Claude Code was trying to continue the same large session.

I found a similar pattern in several older sessions:

| ECONNRESET entries | Transcript size | Maximum recorded context |
|---:|---:|---:|
| 126 | 61.3 MB | 763k tokens |
| 9 | 6.8 MB | 328k tokens |
| 8 | 4.6 MB | 408k tokens |
| 3 | 8.6 MB | 367k tokens |
| 3 | 4.0 MB | 399k tokens |

I also found smaller-session outliers, so I do not think payload size explains every reset. Still, most of my failures occurred in sessions with hundreds of thousands of tokens and multi-megabyte transcripts.

---
My hypothesis

My current hypothesis is that some large session requests are being dropped after Claude Code sends them, while small requests on the same connection or machine can still succeed. This would explain why repeatedly sending continue often fails, but moving the work into a fresh session helps.

---

It would be really useful if Claude Code reported whether a reset happened:

  • During upload
  • After the request body was fully sent
  • While reusing an idle connection

It would also help if Claude Code capped identical retries and suggested starting a fresh, compact session after repeated post-upload resets. In my case, continuing to retry the same large session did not resolve the problem.

PaulMEdwards · 1 month ago

My company uses AWS VPN and I find this is most likely to happen in a Claude session that was started while on the VPN and attempted to be continued later, such as the next day, after the VPN is disconnected. If I reconnect to the VPN and try or retry resuming the session, it usually works, even after experiencing the ECONNRESET error.

je-empty · 27 days ago

I have not seen this measured anywhere in the thread, so adding it for the idle-drop / SO_KEEPALIVE bucket. The state it leaves behind is cumulative, and the sysctl on its own does not clear it.

Setup: macOS 26 (Darwin 25.3.0), M3 Air, Claude Code desktop 1.24012.11, consumer-router NAT, several concurrent sessions.

=== TCP state tally ===
7947 LAST_ACK
3735 TIME_WAIT
 580 FIN_WAIT_1
  75 ESTABLISHED
total TCP sockets: 12386

=== LAST_ACK remote IP, top ===
7895  160.79.104.10     <- api.anthropic.com
  16  34.149.66.165
   4  3.0.66.145

=== sample rows ===
tcp4  0  0  <local>.51700   160.79.104.10.443   LAST_ACK
tcp4  0  0  <local>.51685   160.79.104.10.443   LAST_ACK

99.4% of the stranded sockets pointed at the API. Ephemeral port consumption was 12,444 of 16,384 (76%) in the macOS default range 49152-65535, growing about 4 per minute while the machine sat idle. That is roughly 5,760 a day, which matches the 8,000 or so accumulated over two days. Defaults in effect were net.inet.tcp.keepidle: 7200000 and net.inet.tcp.always_keepalive: 0.

How it gets there

A pooled socket goes idle. The router expires the NAT mapping. The pool closes the socket later, the FIN reaches no one, no ACK comes back, and the socket is stranded in LAST_ACK. macOS never reaps these. A few per minute is enough to exhaust the ephemeral range in about two days.

Past that point new connections fail regardless of network health. That is why it presents as almost every request rather than occasionally, and why transport checks at the moment of failure come back clean. Ping, a fresh TLS handshake and an MTU probe all still pass, because a fresh handshake from a spare port is fine.

If you applied the sysctl and still failed

These sockets do not drain. An interface down and up cycle does not clear them either. I measured 8,113 going to 8,122 across a Wi-Fi bounce. Only a reboot cleared them. So always_keepalive=1 with keepidle=60000 stops new accumulation but does not recover a port range that is already exhausted. You need both.

After applying the sysctl through a LaunchDaemon and rebooting, LAST_ACK went from 8,178 to 1, ephemeral ports from 76% to 1.1%, and the ECONNRESET failures stopped.

@paulosantos-ios you applied the same sysctl and kept failing, so it may be worth checking whether the port range was already exhausted from before the change.

Quick check

netstat -an -p tcp | grep -c LAST_ACK

Thousands means this bucket. Tens means something else.

Suggestion

Setting SO_KEEPALIVE with an interval below typical NAT timeouts on pooled API sockets, or bounding idle socket lifetime in the pool, would remove the need for a system-wide sysctl that affects every application on the machine.

One more datapoint on connection count, measured 6 minutes after a clean boot with 6 concurrent session processes:

33 claude/2908
33 claude/2733
32 claude/3082
32 claude/2316
31 claude/2526
31 claude/1451
total: 195 connections to 160.79.104.x

That is about 32 concurrent connections to a single host per session process. Whether or not it is intended, it sets the rate at which sockets can be stranded, so it multiplies this failure mode across concurrent sessions.

KrisWonka · 5 days ago

**Adding a bucket I don't see in this thread: the path is altering bytes, not dropping
them.** It looks identical to the PMTU bucket @CamiloEspinoza and @mmisztal1980 described,
but lowering the MTU does nothing for it, so it seems worth being able to tell them apart.

My symptoms were the usual ones: DNS fine, TCP connect 7 ms, TLS handshake 17 ms, 60 MB
downloads at 14 MB/s, small requests never failed. Only large request bodies died, at ~28%.
My path MTU was already a full 1500, so there was nothing to lower.

30-second test

TLS is already a perfect detector for this, so no tooling is needed:

head -c 524288 /dev/urandom > /tmp/up.bin
for i in $(seq 1 12); do
  curl -sS -o /dev/null --max-time 45 -w '%{exitcode} ' \
    -X POST --data-binary @/tmp/up.bin https://speed.cloudflare.com/__up
done; echo
  • all 0 → not this bucket; keep going down the list above
  • any 56 → run one with -v; if it says sslv3 alert bad record mac, the bytes you sent

are being changed in flight

Why TCP doesn't catch it

A NAT device has to rewrite addresses and ports, and those fields feed the TCP checksum, so
it necessarily recomputes and rewrites the checksum before forwarding. If it also damages
the payload, the checksum it writes is computed over the already-damaged bytes and arrives
self-consistent. Interface error counters stayed at exactly zero on both ends the whole
time, which is why this survives the kind of transport-level check that would catch loss.

TLS does catch it, and then can only kill the connection: it sits above TCP, which has
already ACKed those bytes and dropped them from its send buffer, so there is nothing left
to retransmit. Node surfaces that as ECONNRESET. One damaged packet in ~1,100 was enough
to destroy an entire multi-megabyte request, over and over.

What ruled out my machine

  • 60 MB to a host on my own LAN arrived byte-identical
  • a different machine on the same network (ARM64 Linux, different Wi-Fi chipset, different

TLS stack) failed at 75%

  • disabling TSO made it worse, 28% → 53%

What fixed it

A WireGuard tunnel — same as the "VPN made it disappear" reports in these threads, but the
mechanism is specific: WireGuard authenticates every packet below TCP, so a damaged packet
fails its tag, gets dropped, and TCP just retransmits. Corruption becomes packet loss, which
TCP handles fine. That is also why it masks a PMTU blackhole, which I think is why "turn on
a VPN" keeps working for people with different underlying faults.

Tooling

I put the elimination ladder into a dependency-free script — path MTU, failure rate, whether
rate-limiting avoids it, a second destination, the physical link underneath any active
tunnel, and a LAN-only transfer that clears or convicts your own NIC:
https://github.com/KrisWonka/pathrot

Caveats up front: the corruption half has only been validated against one bad path, mine,
and the fault is intermittent — mine drifted between 17% and 75% over a day. The 12-line
loop above is enough to tell you whether you are in this bucket at all; if several people
run it and see 56, that is worth knowing, and if everyone sees 0 then this bucket is
rarer than I think and I have cost you a scroll.