Skip to content

API Error: Stream idle timeout - partial response received

A timer on the consuming side gave up waiting for the next chunk of a stream that was already underway. The request was authenticated, accepted and partly answered before this fired — so it is not an auth error, not a quota problem, not a malformed request, and not something a new API key will change. It is also not, by itself, evidence that anything on the network broke: a deadline expired, and the client tore the stream down on purpose.

Data as of 2026-09. Vendor limits and defaults change; check the official docs for current values before acting on any number below.

“Idle” is a gap, not a duration

This is the part almost everyone gets backwards, and it changes the whole diagnosis.

An idle timeout does not cap how long a response may take. It caps the silence between two consecutive chunks, and every byte that arrives resets the clock. A stream that trickles out slowly but steadily can run far longer than the threshold and never trip it; a stream that is fast for ninety percent of its output and then goes quiet once, for one interval longer than the threshold, dies. Total elapsed time is not what is being measured.

Follow that through and a familiar heuristic inverts. With most timeout errors, “it only fails on big requests” is a useful signal. Here it is a trap. Request size and output length only matter insofar as they make a long quiet gap more likely — and plenty of long generations produce no gap at all, while short agent turns that call a slow tool produce a very large one. What you are hunting is the single longest silence in the turn, not the turn’s size.

The second-order consequence is the one that costs people a day: the most common producer of a fatal gap is not a slow model, it is something in the path that buffers the stream. A proxy, a gateway, a compression layer or an SDK wrapper that accumulates the response before passing it on converts a perfectly healthy steady stream into exactly one long silence followed by one burst. The stream was never idle. Your client only saw it that way, and it will fail more reliably the longer the turn runs — which reads, misleadingly, like a size limit.

What “partial response received” actually buys you

The phrase is not a consolation prize. It is a timestamp, and it is the best evidence you have.

The content sitting in your buffer names the phase that stalled. If it stops mid-sentence in ordinary prose, the gap happened during token generation. If it stops right at the boundary of a tool-use block — the model finished asking for a tool and nothing followed — the silence is the tool round trip, and no amount of HTTP client tuning addresses a gap your own agent loop created. If the output stops at the same offset on every attempt, you are looking at something deterministic; if the cut-off wanders, you are not.

Note also how the partial arrived. Incremental arrival followed by a stall is a genuine gap. A long nothing, then everything at once, then the error is the buffering case above, and it is the case where raising the timeout appears to help for a while and then stops helping.

One thing the partial does not buy you: resumption. There is no protocol mechanism to continue a stream from where it was cut. A retry restarts generation and re-sends the full input.

Is retrying useful?

Yes — retry once, and treat the second attempt as a measurement rather than a fix.

The two outcomes are both informative. If the retry succeeds, the gap was transient and you are done unless it recurs. If it dies at roughly the same relative point, you have proven the stall is deterministic, and every further identical retry is spending input tokens to reproduce a known result. That is the point to stop and use the sections below.

Two practical notes. First, do not lean on your SDK to do this for you. The official SDKs retry transient failures with exponential backoff, twice by default, honoring retry-after when present — but mid-stream failures arrive after a 200 response and never enter that path. The connection closed mid-response page explains why that ordering makes your max_retries setting irrelevant here; the short version is that any retry around a stream has to be one you wrote.

Second, on cost: a retry re-sends the whole input, but for most models a prompt cache read is billed at 10% of the base input price, so a retry with a cached prefix is cheaper than the raw token count suggests. Honor retry-after when the vendor sends one. Do not hard-code a wait — the published guidance is exponential backoff and no base delay is documented to copy.

Locating the silence

Each of these has an observation that confirms or rules it out.

Failure arrives after a consistent quiet interval, reproducibly. A configured idle or read timeout on the path fired. Test it by changing the value at exactly one hop and re-running: if the time-to-failure moves, that hop owns the timeout. If it does not budge, the timer that fired is not the one you edited — and there is usually more than one.

The partial arrives in a single burst right before the error. A buffering intermediary. Watch the arrival time of the first byte; if first output lands many seconds after the request rather than in under a second, the stream is being held rather than streamed.

It only happens on turns that call tools, never on plain generation. The gap is your tool round trip, not the vendor. Time the slowest tool call in the failing turn and compare it against the interval you measured above.

It only happens on one network — a VPN, an office gateway, a new container image. Reproduce the same request from a different path. This is the cheapest test on the page and people skip it.

It never stalls twice in the same place and other requests are unaffected. Upstream variance, which is closer to the neighbouring failures in this cluster than to a timeout. If your tool is reporting waits and counting attempts rather than reporting a stall, you are probably looking at No response from API, retrying in 2m 25s or the connection error retry loop instead, and those have different advice.

Fix by scenario

  • Consistent quiet interval, one hop identified — raise the idle timeout at every hop in the path, not only in the code you edited. Fixing one of three timers is the usual reason the change appears to do nothing.
  • Burst arrival / buffering intermediary — disable response buffering and compression on the streaming route. Raising the timeout here is the cheap wrong fix: it converts a fast failure into a long hang, and the next longer turn fails anyway because the silence grows with the response.
  • Tool round-trip gaps — bound the tool’s execution time, or split the turn so no single step can go quiet for longer than the threshold. The stream cannot report progress on your behalf.
  • Genuinely long work — 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 beyond that. You are already streaming, so the remaining lever is decomposition or batching, not a larger number.
  • Anything you intend to report — capture the request-id response header (hyphen in the header, underscore in the JSON body field; the Python and TypeScript SDKs expose it as _request_id) along with the elapsed time between the last two chunks you received.

The threshold itself is worth naming as a gap in the record: the idle interval that trips this message is not published in Anthropic’s API documentation, so treat any specific number you find in a blog post as unverified and measure your own inter-chunk gaps instead.

How to confirm it’s fixed

Re-run the specific turn that had the longest silence — not a smaller, easier request — and require it to complete twice in a row on the same network path. One success is indistinguishable from a transient stall resolving itself.

Then make it falsifiable: log the elapsed time between consecutive chunks and confirm a completed run where the largest observed gap exceeds the interval that used to kill you. That is the difference between “it worked this time” and “the timer that fired no longer fires”. If you changed a timeout value, verify it is in effect at each hop by inspecting the running configuration rather than the file you edited.

These failures look alike in a terminal and are not the same thing. A connection closed mid-response is a socket going away — nobody decided anything, there is no threshold to tune, and the failure point tends to wander; an idle timeout is a deliberate give-up after a measurable quiet interval, which means it is reproducible and configurable. If you can move the time-to-failure by editing a number, you have this error. If you cannot, read that page instead. When the symptom is a client that keeps waiting and re-announcing retries rather than cutting the stream, start with the connection error retry loop. If you are not sure which of the three you are holding, the AI coding error triage tool sorts them by the signals above — arrival pattern, consistency of the failure point, and whether partial output survived.