TypeError (fetch failed)
Nothing answered. fetch failed is what Node’s HTTP client says when a request
produced no response at all — no status line, no headers, no body. That rules
out an expired key, a 429, a 529, a billing block and a server-side 500, because
every one of those is a response that arrived. The TypeError part is Node’s
choice of class and says nothing about types: do not go looking for a type bug
in your own code.
Data as of 2026-09. Vendor limits and defaults change; check the official docs for current values before acting on any number below.
The same string, opposite verdicts, decided by when it fires
There are two populations of this error and they need opposite responses.
The first is a path that never worked: a certificate the runtime will not trust, a proxy the process does not know about, a blocked host, a resolver that returns nothing. Those causes are deterministic — they are decided from unchanged inputs and produce the same answer every time — which is why the sign-in version of this message, OAuth error: fetch failed, concludes that retrying is a waste of time and covers the whole category, including why your browser opening the same page proves nothing about a Node process.
The second population is the one this page is for, and it is identified by a single fact: the same process was working minutes ago. That is not a small detail, it is an exoneration. Proxy settings, certificate bundles and DNS configuration are evaluated identically on every request, so nothing in your configuration changed between the request that succeeded and the request that failed. A config-shaped cause cannot produce a config-shaped symptom only after forty successful requests. If you are re-checking your certificate bundle at that point, you are auditing something the evidence has already cleared.
What actually breaks after it was working
Once configuration is out of the picture, the candidates are all about the connection rather than the settings.
HTTP clients keep sockets open and reuse them. A NAT device, a stateful firewall, a VPN or a corporate proxy will silently drop an idle mapping after some interval, and — this is the part that makes it hard to spot — it does not tell anybody. The socket looks alive on your side. The next request writes into it and gets nothing back. The failure therefore happens on the request after an idle gap, not during the gap, which is why it feels random and why the immediate retry almost always succeeds.
The other members of this family have the same shape. A laptop that slept, a Wi-Fi to cellular handover, a VPN reconnect, a DHCP lease change: all of them invalidate sockets that the client still believes in. So does a DNS record whose TTL expired onto an address your egress rules do not permit, which produces a first failure long after startup and then persists.
The discriminator between this family and a genuinely degraded network is whether the retry succeeds immediately. A dead pooled socket fails once and then gets replaced. A broken path fails repeatedly.
Is retrying useful?
Yes — and the retry’s outcome is the measurement, so watch it rather than looping.
Retry once, deliberately. If it succeeds immediately and the session continues normally, a stale connection is the leading theory and the sections below tell you how to confirm it. If it fails the same way several times in a row, you are in the first population after all — treat it as deterministic, stop retrying, and read the causes on the sign-in page linked above, because they are the same causes.
If you are seeing this inside Claude Code alongside a retry banner, the CLI has
already classified the failure as retryable and is running its own loop of up to
ten attempts with exponential backoff. In that case the retrying is happening
without you;
the connection error retry loop
covers what the counter proves and when to stop letting it run. Note that a
certificate validation failure is reported on the first attempt with no
retries — so a fetch failed that appears instantly and alone, with no counter,
is behaving like a certificate problem rather than a flaky link.
There is no retry-after to honor, because no response arrived to carry one,
and no base delay is published anywhere to copy. Any specific wait you insert is
a number you invented.
Telling the causes apart
Get the layer underneath first. fetch failed is a wrapper; the real reason
hangs off the error as its cause. Run Claude Code with --debug, which writes a
per-session log to ~/.claude/debug/<session-id>.txt — on Windows ~ means
%USERPROFILE%. A refused connection, a reset socket and a certificate rejection
have identical top-level text and unrelated fixes.
Does it follow idle periods? Leave the session untouched for several minutes, then send one small request. If that request is reliably the one that fails while the next one succeeds, you have found a reaped connection and you can stop looking at anything else.
Does it correlate with network events? Compare failure times against VPN reconnects, suspend and resume, and Wi-Fi handovers. This is a log-reading exercise, not a configuration change, and it is cheap.
Does it happen on every request from process start? Then the first population applies and the evidence points back at configuration after all.
Is it only large or long requests? Then the connection is not being reaped, it is being cut while in use, which is a different failure — see the related errors below.
Does it reproduce on a different network? A phone hotspot answers this in under a minute and is the test people skip because they are already sure it is the vendor.
Fix by scenario
- Failures follow idle gaps — shorten the client’s idle connection lifetime so it discards sockets before the middlebox does, or enable TCP keep-alive on the path so the mapping is refreshed. The Anthropic SDKs already set a TCP keep-alive socket option for long-running requests; whatever sits in front of you may still be idling out the connection regardless.
- Failures follow suspend, VPN reconnect or a network change — have the client establish a fresh connection after those events rather than trusting the pool. If you cannot control the client, restarting the process after a network change is a legitimate workaround and takes seconds.
- Every request fails from startup — this is the configuration case. Fix the specific cause named in the debug log, and make the runtime trust your organization’s certificate authority rather than disabling verification, which would trade a visible failure for a silent exposure of every credential the process handles.
- Only inside a container or CI runner — the image is the suspect. A base image bump can change the certificate bundle and the proxy environment at the same time, and neither shows up in your application diff.
- Started after a plugin, MCP server or hook was added — launch with
claude --safe-mode, which disables plugins, MCP servers and hooks. If the failures stop, a customization is altering the environment; if they do not, you have eliminated a category for the price of one launch. - Reporting it — there is no
request_idto attach, because there is no response. Record the timestamp with timezone, the elapsed idle time before the failing request, whether the immediate retry succeeded, and the underlying cause from the debug log.
How to confirm it’s fixed
Reproduce the condition, not the request. Idle the session for longer than the gap that used to kill it, then send a request, and require that to work twice on separate occasions. A quick burst of successful requests proves nothing about a failure mode that only appears after a pause, and it is the standard way people conclude this is fixed a day before it recurs.
If your change was to a client or proxy setting, verify the value in effect at runtime from a freshly launched process rather than reading the file you edited. For the configuration case, confirm on the network that was failing, not on the hotspot you used to diagnose it.
Related errors
The neighbours here are distinguished by how much of the response survived. If output had already started arriving and the socket then went away, you have connection closed mid-response — a partially answered request, which is a different problem with different advice. If your code caught a typed exception rather than seeing raw CLI text, read anthropic.APIConnectionError: Connection error, where the traceback frame tells you whether the SDK had already retried twice. And if the symptom is a client that keeps waiting rather than failing, see No response from API, retrying in 2m 25s. When you cannot tell which you are holding, the AI coding error triage tool sorts them by the signals used above: whether a response arrived, whether output arrived, and whether the process had been working before.