llm-timeout-budget

Works locally, 504s in staging

Model how long your worst-case LLM request really takes end to end, check it against every timeout sitting between your code and the API, and find out whether streaming saves you or does nothing at all.

No network calls No API key No backend Planning estimate, not a guarantee Facts confirmed 2026-08-07
This tool measures nothing. It never sends a request, to your provider or to anyone else. Time-to-first-token and tokens-per-second are numbers you enter from your own observation. Every prefilled figure is a placeholder. There are no benchmark tables here, no per-model throughput numbers and no latency comparison between providers or models, because publishing throughput figures nobody measured would be fabricated benchmarking.
01

The request model

All six figures below are yours. The tool does not infer time-to-first-token from your input token count and does not know how fast any model is. Measure once against your own route, paste the numbers in, then use the scenario multipliers to ask what the bad day looks like.

illustrative placeholder - replace with your own measurement
illustrative placeholder - replace with your own measurement
illustrative placeholder - these burn wall clock while emitting nothing visible
illustrative placeholder - used only by the SDK rescaling check below
illustrative placeholder - replace with your own measurement
illustrative placeholder - replace with your own measurement
illustrative placeholder - your own headroom for a busy provider
pick the one that matches how you measured, so nothing is counted twice
selects the three multipliers below; edit any of them to go custom
above 1 means slower
below 1 means slower, so worst case is below 1
above 1 means a longer queue
02

The timeout stack

Every layer between your process and the provider gets a row. The kind column is the whole lesson. An idle timeout is reset by every byte received. A total timeout counts wall clock from the start of the request and is reset by nothing. An execution ceiling meters compute time and ignores the distinction entirely. Streaming defeats exactly one of those three.

2 is the documented default in every current Anthropic SDK (confirmed 2026-08-07)
this tool does not compute backoff - bring the worst-case wait from retry-backoff-calculator
see trap (c) below
Blank or zero renders UNKNOWN, never PASS.
Layer Kind Value Unit Retries stack Verdict Actions

Unit converter

The Anthropic TypeScript SDK takes milliseconds. Python and Ruby take seconds. A value copied between them is off by a factor of a thousand.

Does streaming help?

KindNon-streamingStreamingHelps?
IdleDies at the limit - nothing arrives until the endTimer resets on every byte receivedYES
TotalDies at the limitDies at the same limitNO
ExecutionDies at the limitDies at the same limitNO
"Just use streaming" is the wrong answer in two of the three rows.

Retry-multiplied budget

Timeouts are themselves retried, so the number that actually crosses an infrastructure ceiling is the per-attempt duration times the number of attempts. Idle timeouts reset on each fresh attempt, so only layers whose budget accumulates across the retry loop are drawn here.

03

The client-library trap panel

These four are properties of the libraries themselves rather than claims about any vendor's infrastructure, which is why each one carries the package and version it was read in. Library behaviour changes: re-check these against the version you actually have installed.

(a) The timeout you set is per chunk, not a deadline

observed in requests 2.34.2 and httpx 0.28.1, confirmed 2026-08-07

The timeouts people reach for in the two dominant Python HTTP libraries are read timeouts that reset every time bytes arrive. The requests documentation defines the read timeout as "the number of seconds that the client will wait between bytes sent from the server" and states plainly that "neither the connect nor read timeouts are wall clock". The httpx documentation defines its read timeout as "the maximum duration to wait for a chunk of data to be received" and offers four knobs - connect, read, write and pool.

The failure: a response that trickles, or a proxy that dribbles keep-alive bytes, blocks your call indefinitely under a setting that reads like a hard deadline. Neither library documents a total-wall-clock option at all, so there is nothing to switch on.

Fix: keep a monotonic clock at the loop level and bail out yourself, or wrap the whole call in an async timeout.

import time
import httpx

DEADLINE_SECONDS = 600.0   # your wall-clock budget, enforced by you

def stream_with_deadline(url, payload):
    started = time.monotonic()
    # 30.0 here is a PER-CHUNK read timeout. It is not a deadline.
    with httpx.stream("POST", url, json=payload,
                      timeout=httpx.Timeout(30.0)) as response:
        for chunk in response.iter_bytes():
            elapsed = time.monotonic() - started
            if elapsed > DEADLINE_SECONDS:
                raise TimeoutError(
                    "wall-clock budget exceeded after "
                    + str(round(elapsed, 1)) + "s"
                )
            yield chunk

# async equivalent:
#   await asyncio.wait_for(do_request(), timeout=DEADLINE_SECONDS)

(b) The unit is not the same in every language

observed in anthropic 0.121.0 (Python), @anthropic-ai/sdk 0.116.0 (TypeScript), anthropic 1.61.0 (Ruby gem), anthropic-sdk-go v1.62.0, com.anthropic:anthropic-java 2.52.0, Anthropic 12.40.0 (NuGet); confirmed 2026-08-07

Every one of those SDKs documents a 10 minute default, and each expresses it differently. (Go scopes its 10 minutes to non-streaming Messages requests and documents no default timeout at all for anything else.) Python takes a float in seconds (or an httpx.Timeout). Ruby takes seconds. TypeScript takes milliseconds - its own example reads timeout: 20 * 1000 for twenty seconds. Go takes a time.Duration through option.WithRequestTimeout, and that one is a per-retry timeout rather than a whole-call one. Java takes a Duration, C# a TimeSpan.

The failure: a value copied from a Python service into a TypeScript one is off by a factor of a thousand in the direction that hurts. 600 meant as ten minutes becomes six hundred milliseconds.

Fix: the converter beside the stack table, and the warning this tool raises when a millisecond field holds a number that looks like somebody's seconds value.

(c) The SDK quietly moves its own default

observed in @anthropic-ai/sdk 0.116.0, com.anthropic:anthropic-java 2.52.0, anthropic 0.121.0 (Python); confirmed 2026-08-07

The effective deadline is often not the one you configured. The TypeScript SDK documents that for a large max_tokens on a non-streaming request the default timeout is computed as (60 * 60 * maxTokens) / 128000 seconds, floored at ten minutes and reaching up to sixty. The Java SDK documents the mirror image: the same shape of formula applies to streaming requests, while its non-streaming default scales from a thirty second minimum to a ten minute maximum.

Meanwhile the Python SDK refuses rather than hangs: it "will throw a ValueError if a non-streaming request is expected to take longer than approximately 10 minutes", disabled by passing stream=True or by overriding the timeout. The TypeScript, Go and Java SDKs document the same guard.

Fix: set the timeout explicitly so the default never applies, and tick the rescaling option in section 02 to see what your configured value actually becomes.

(d) Timeouts are retried, so multiply

observed in anthropic 0.121.0 (Python), @anthropic-ai/sdk 0.116.0 (TypeScript), anthropic 1.61.0 (Ruby gem), anthropic-sdk-go v1.62.0, com.anthropic:anthropic-java 2.52.0, Anthropic 12.40.0 (NuGet); confirmed 2026-08-07

All six SDKs document the same policy: connection errors, 408, 409, 429 and 5xx are retried automatically, two times by default. The Python and TypeScript docs both add the sentence that catches people out - "requests that time out are retried twice by default". The Ruby docs list timeouts explicitly among the retried conditions.

The failure: your worst-case wall clock is not the timeout, it is the timeout times three. That product is usually what crosses the serverless ceiling or the gateway limit, long after the per-attempt number looked comfortable.

Fix: set the retry count deliberately, and budget the whole loop rather than one attempt. Feed the worst-case backoff wait from retry-backoff-calculator into the backoff field in section 02.

04

Output

A planning estimate you can paste into a ticket. It is arithmetic over numbers you supplied, not a guarantee about your production stack.


  

What to change, ordered by effect

Generated from your stack, not from a fixed list. Changes that move the layer which kills the request first are listed before changes that gain more seconds elsewhere, because a change downstream of the binding layer buys nothing until the binding layer is fixed. Within each group the list is ordered by seconds of runway gained.

Markdown summary

REF

Dated vendor reference

This table is reference material, not part of the arithmetic above. Every one of these values is configurable and varies by plan tier and by how somebody set it up years ago. The tool computes only against the numbers in your stack table. Confirm each of these in your own configuration and correct the row if it differs - a stale row here can never make the maths above wrong, because the maths above does not read it.

LayerSettingCommonly seen default KindConfirmedSource
nginx reverse proxyproxy_read_timeout60s Idle2026-08-07 nginx docs
nginx reverse proxyproxy_bufferingon n/a2026-08-07 nginx docs
AWS Application Load Balanceridle_timeout.timeout_seconds60 seconds Idle2026-08-07 AWS docs
AWS LambdaFunction timeout900 seconds (15 minutes) quota Total2026-08-07 AWS docs
Cloudflare edgeProxy Read Timeout (error 524)125 seconds; Enterprise can raise to 6000 Not stated - see note2026-08-07 Cloudflare docs
Not every timeout tells you which kind it is. The name "Proxy Read Timeout" reads like an idle timer, but Cloudflare's own 524 page does not say whether the countdown restarts when the origin sends bytes. What it does say is that if your requests exceed 125 seconds, "for example, streaming", you have to raise the value, which is not how an idle timer that streaming defeats would behave. Treat it as a total limit until you have confirmed otherwise on your own zone, and set the kind in your stack table accordingly. That caution generalises: when a vendor names a timeout but does not document its reset behaviour, the safe assumption is the one that does not depend on streaming saving you.
The buffering trap. nginx documents proxy_buffering as defaulting to on, in which case it "receives a response from the proxied server as soon as possible, saving it into the buffers". Only with buffering off is "the response passed to a client synchronously, immediately as it is received". If a proxy in your path buffers, the client sees nothing until the response completes, and streaming defeats no idle timeout anywhere downstream of it. Check that setting before trusting any streaming verdict on this page.
On heartbeats. Server-sent events allow comment frames - lines that begin with a colon and carry no data - and a stream that emits them keeps an idle timer alive during a long silent thinking window. Whether your provider actually emits them, and whether every proxy in the path forwards them instead of swallowing or buffering them, is something you have to confirm for your own stack. This tool does not assume either way, and does not claim any particular provider sends them.
SEE

Related tools