api error: connection closed mid-response
This error means the HTTP connection carrying a streamed response was torn down after the response had started but before it finished. The request was accepted and partially answered — which is why it is not an auth error, not a quota error, and not something you can fix by checking your API key.
Data as of 2026-09. Vendor limits and defaults change; check the official docs for current values before acting on any number below.
The part that confuses everyone
A streamed request returns its HTTP status before the model produces a
single token. By the time the stream dies, the server has already answered
200 OK. Nothing about the failure looks like an error to the transport layer:
the status was a success, and then the socket closed.
That has a consequence people discover the hard way. The official Anthropic
SDKs retry transient failures — connection errors, rate limits, 5xx — with
exponential backoff, twice by default, honoring retry-after when it is
present. Mid-stream errors arrive after the 200 and do not go through that
mechanism. Your max_retries setting is not broken and it is not misconfigured;
it simply never sees this failure. If you want a mid-response close to be
retried, you have to write that retry yourself, around the whole streaming call.
This is also why the error surfaces as a tool-level message like
api error: connection closed mid-response rather than a typed API error with
a type and a message. There is no error body to parse. The only artifact
you get is whatever partial output arrived before the connection went away.
Is retrying useful?
Usually yes — but not immediately, and not blindly.
A mid-response close is most often transient, so one retry after a short pause resolves the majority of cases. What matters is what you do when it doesn’t. Retrying an identical request that fails at the same point every time is the single most common waste of time with this error: you are paying for the input tokens of every attempt to reproduce a deterministic failure.
So retry once. If the second attempt dies too, stop and diagnose. The rest of this page is about that diagnosis.
Telling the four causes apart
The error text is identical in all four cases, so the signal is not in the message — it is in the pattern of failures.
It fails at roughly the same point every time. The generation is outliving a timeout somewhere on the path. Something between your process and the model — your own HTTP client, a reverse proxy, a load balancer, a corporate egress gateway — has a read or idle timeout shorter than the time the response needs. Long streamed responses look idle to a naive proxy, because the proxy sees a trickle of bytes rather than a request-response pair.
It fails at random points, intermittently, and other requests succeed. Upstream capacity. This is the same family of condition that produces 529 overloaded errors, which reflect platform-wide traffic rather than anything about your own account’s usage. Back off and retry; there is nothing to fix on your side.
It started right after an infrastructure change. New VPN, new proxy, a container base image bump that changed the HTTP client defaults, a move to a different network. Suspect the change before you suspect the vendor. Reproduce the same request from a different network to confirm.
It only happens on large requests or long outputs. You are near a limit that shorter requests never reach. Note that the SDKs validate that non-streaming Messages API requests are not expected to exceed a 10-minute timeout, and the docs recommend streaming or the Message Batches API for work that runs longer than that. If your generation is genuinely in that territory, the fix is architectural, not a matter of nudging a timeout value.
Fix by scenario
- Consistent failure point — raise the client read timeout and the idle timeout on every proxy in the path. Raising it in only one place is the usual reason the fix appears not to work.
- Intermittent, random — retry with backoff around the streaming call, and
honor
retry-afterwhen the vendor sends it. Do not hard-code a wait of N seconds; the published guidance is exponential backoff, and no base delay is documented to copy. - Large requests only — reduce context, cap output length, or split the work. If the tool lets you disable streaming for that call path, a non-streaming call will at least fail with a real status code you can act on, such as a 504 timeout.
- Anything you plan to report — capture the
request-idresponse header (hyphen in the header, underscore in the JSON body field; the Python and TypeScript SDKs expose it as_request_id). Without it, a support ticket is a description of a feeling.
What not to do
Do not treat a partial response as a complete one. If your code accumulates streamed deltas and the stream dies, the buffer holds a truncated answer that often looks finished — a code block that parses, a sentence that ends. Check that the stream terminated normally before you use what it produced. Silent truncation is worse than a visible error, and this failure mode is how it gets into a pipeline.
Do not raise timeouts everywhere as a reflex either. A generous timeout turns a fast failure into a slow one when the real cause is capacity.
How to confirm it’s fixed
Re-run the request that failed, at the same size, on the same network path. A fix is confirmed when it completes twice in a row — once is indistinguishable from the transient case resolving on its own. If you changed a timeout, confirm the new value is actually in effect at every hop rather than only in the code you edited.
Related errors
Failures in this cluster look alike and are not the same thing. A timeout that fires before the first token is a different problem with different advice: that one is never worth retrying without changing something first. A rate limit error arrives as a status code with headers you can read, not as a dead socket. If you are not sure which one you have, the AI coding error triage tool sorts them by the signals above.