503 upstream connect error or disconnect/reset before headers. reset reason: remote connection failure, transport failure reason: delayed connect error: Connection refused
A proxy sitting in front of the model service accepted your request, went to open a connection to the backend it had selected, and that connection was actively refused. Your credentials were never presented, your prompt was never read, and no tokens were counted — which rules out auth, quota, context length, model choice, and anything you could fix by editing the request. The shape of the message also tells you who wrote it: this is the house style of Envoy-family proxies, not the wording the Claude API uses for its own errors.
Data as of 2026-09. Vendor limits and defaults change; check the official docs for current values before acting on any number below.
“Before headers” is the most valuable phrase in the line
It is a statement about how far the request got. No response headers were ever produced, which means the backend never began answering — and since it never began answering, it never began doing anything. Nothing was executed, nothing was billed, no partial work exists on the other side.
That matters more than it sounds, because it places this failure on the safe side of a line the tooling itself draws. Claude Code deliberately does not retry a server error, dropped connection or stalled stream that arrives after Claude has completed a block of text or a tool call, precisely because re-running could execute the same tool calls twice. A failure before headers carries no such risk. This is one of the few errors in this cluster where hitting up-arrow and Enter cannot cost you anything except time.
Refused is not the same as unreachable, and the difference is the diagnosis
Connection refused is a specific outcome, and most people read past it as
generic failure. It means a TCP connection attempt reached a live host and that
host answered with an explicit refusal. Routing worked. DNS worked. The machine
is up. There is simply nothing listening on the port that was dialled.
Compare the alternatives the message could have carried instead. A timeout means packets went into a void — a firewall dropping them, a black-holed route. A DNS failure means the name never resolved. A TLS error means something answered and the handshake was rejected. Each of those points somewhere different. A refusal points at a listener that is not there.
Now apply that to scale. Large public API endpoints essentially never refuse connections. They sit behind load balancers that accept the connection and then return a status code, or they time out. A refusal implies something small, specific and close: a relay you run, a sidecar that has not started, a container that exited, a port-forward or tunnel that died, or a base URL pointing at a port nobody is serving.
delayed connect error completes the picture. The proxy accepted your request
first and discovered the backend problem afterward, on a connect attempt made
asynchronously. That is why you received a 503 with a proxy’s wording rather
than a plain connection error from your own HTTP client — by the time the
refusal happened, the proxy already owed you a response.
Is retrying useful?
Yes — retry, but expect the answer within seconds, not minutes.
Retrying is safe here for the reason above: nothing ran, so nothing duplicates. And a refusal is genuinely transient in one common case — a service restarting, a container being replaced, a deploy rolling through. During that window the port is briefly unbound and then it is back, and the request that failed will succeed on the next attempt without you doing anything.
The termination condition is unusually sharp, and it is the practical value of knowing what a refusal is. A refused connection is one of the fastest failures a network can produce: no waiting, no timeout, an immediate answer. So a retry loop against a refusal burns through its attempts in seconds and tells you almost immediately which world you are in. If two or three attempts come back instantly with the identical message, the listener is not coming back on its own and further retries are a fast loop producing no new information.
That is the opposite of how you should treat capacity errors, where waiting genuinely helps. Do not hard-code a wait here either; no base delay is published for you to copy, and the documented guidance is exponential backoff.
One more thing to know before you escalate: Claude Code retries transient failures up to ten times with exponential backoff before showing you anything. If this message reached your screen from that tool, the transient window has already been sampled repeatedly on your behalf.
Whose listener is missing
- Print the base URL actually in effect at runtime. Not the value in your config file, not the one you think is set — read it from the running process or the tool’s own status output. A custom base URL, a relay, a gateway, an LLM proxy or a corporate egress endpoint immediately becomes the prime suspect, because it is the thing that could plausibly refuse.
- Reproduce with a plain HTTP client from the same machine. Point
curlat the same base URL. If it is refused identically, your tool is not involved and you can stop reading its logs. Ifcurlsucceeds while the tool fails, the tool is using a different URL or a different proxy setting than you think. - Check for stale proxy environment variables.
HTTP_PROXY,HTTPS_PROXYand their lowercase twins pointing at a local interception proxy that is not running produce exactly this error, and they produce it against every endpoint, which makes it look convincingly like the vendor is down. Print them in the environment the failing process actually runs in. - Try the official endpoint with the same credentials. If the direct route works and your gateway does not, the answer is settled and nothing on your machine or in your account needs changing.
- Note what restarted. A deploy, a
docker composecycle, a VPN reconnection, a machine waking from sleep, a tunnel that timed out. A refusal that began at a specific moment usually began at the same moment as one of those. - Check whether request size matters. It should not — a connection refusal happens before any body is sent. If small requests succeed while large ones fail, you are looking at a different fault and this page is the wrong one.
Fix by scenario
- Your own relay, gateway or sidecar — confirm the process is running and bound to the exact port the base URL names. Listening on a different interface than the one being dialled is the usual version of this: a service bound to a container’s loopback is not reachable from outside it, even though the port “is open” from inside.
- Stale proxy environment variables — unset or correct them, then start a new shell, or restart the service or scheduled task, so the change is actually inherited. Editing a variable in one window and testing in another is the standard reason this looks unfixed.
- A tunnel or port-forward that died — re-establish it, then make it supervised. Anything holding a long-lived forward will drop it eventually, and the next occurrence will look like a brand-new problem.
- It began with a restart and has not cleared — the service came back on a different port, or failed to come back at all. Read that service’s own logs; the proxy in front of it can only tell you it was refused.
- Direct endpoint works, gateway does not — switch routes to unblock yourself and raise it with whoever operates the gateway. Being able to flip between routes with one command turns this from an afternoon into a minute.
- Reporting it — there is no request id in this failure and there never will
be, because no service processed the request. Capture instead the exact
timestamp with timezone, the base URL in effect, the full message, and whether
curlfrom the same machine reproduces it.
The fix that cannot work
Raising the request timeout. It is the reflex for anything that smells like a network problem, and it is aimed at the wrong mechanism entirely: Claude Code’s per-request timeout defaults to 600000 ms, while a refused connection returns in milliseconds. You would be enlarging a budget that was never spent. The same applies to raising the retry count — a deterministic refusal fails identically on attempt fifty.
How to confirm it’s fixed
Run the request that failed, on the same route, and require it to succeed twice in a row. One success after a service restart is indistinguishable from the listener having come back on its own while you were editing configuration.
If your change was to the route or the environment, prove the change took effect rather than assuming it: print the base URL and the proxy variables from the running process, in the same place the request is made. A config file you edited and an environment your process inherited are two different things, and the gap between them is where this error lives.
Related errors
If the proxy did not attempt a connection at all and said so — “no healthy upstream” — that is a health-check verdict rather than a refusal, and it usually clears on its own during deploys: 503 no healthy upstream explains why the absence of a request id is itself evidence. If the connection was established and died partway through the answer, you are on the dangerous side of the “before headers” line and a blind retry can duplicate work; see connection closed mid-response and stream idle timeout with a partial response. If nothing answered at all rather than refusing, and your tool is counting down a long retry interval, read No response from API with a multi-minute retry — a long interval means the failure is not fresh.
To sort a network-shaped failure by layer before spending time on it, the AI coding error triage tool separates them by the signals used here: who wrote the message, whether headers were ever produced, and whether anything refused, dropped or merely stalled.