A 400 is classified as a transport error, retried three times, and returned to the caller as 502 #114
Labels
No labels
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/agent-proxy#114
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Split out of #113 so the fixes stay separable. Same trace:
1a49200c3bebaed778ef2ac5b79d3d99, 2026-08-13T17:07:07Z (SigNoz:http://ser8:30808/trace/1a49200c3bebaed778ef2ac5b79d3d99).The payload in that trace was malformed — that is #113's problem. This issue is about what the dispatcher did with the resulting 400, which would be wrong regardless of how the payload got that way.
1. A deterministic client error is retried three times
Each attempt carried
error: litellm: Client error '400 Bad Request' for url 'http://100.105.159.124:4000/v1/chat/completions'.A 400 is a statement about the request body. The body is byte-identical on every attempt, so attempts 1 and 2 were guaranteed to fail before they were sent. They burned ~2.9s of the caller's budget and three round-trips of backend capacity to re-learn a settled fact.
The retry is downstream of the classification:
dispatch.transport_erroris the wrong bucket. Nothing about the transport failed — litellm answered promptly each time, and answered a different request successfully 7.5s later in the same trace.2. The 400 is reported to the caller as 502
sirens-deepreceived:and surfaced
model backend unavailable, retry shortly.The backend was not unavailable. It served rounds 0–4 of this same turn, and served another request 7.5s after this one failed. Collapsing "your request was invalid" into "all backends failed" points the operator at capacity and availability — see #108, #111, #112, all of which are about genuinely unavailable backends — when the actual defect was in the request agent-proxy itself had just constructed.
The
AllBackendsFailedwrapper is also literally inaccurate here: litellm's own log recordsNo fallback model group found for original model_group=sirens-echo/deepseek, so no fallback was attempted. One backend rejected one payload.Why the two halves belong together
Retrying is only sensible for errors that might resolve on their own; reporting 502 is only sensible when the backend is the problem. Both stem from the same missing distinction between a request defect and an infrastructure defect. Fixing one without the other leaves either a fast-but-mislabeled failure or a correctly-labeled one that still costs 3×.
Acceptance
dispatch.transport_error, and is not retried. 408, 425, and 429 stay retryable.all backends failed.AllBackendsFailedis raised only when more than one backend was actually attempted.Related
errorRateconcern applies here in reverse (this failure is counted, but as the wrong kind).502 backend unavailableshould be reserved for.num_ctxis a local-inference parameter applied to a hosted route #115