fix: retry 400 responses with transient gateway error bodies

Some providers/proxies return HTTP 400 with bodies like "no parseable
body" or "connection reset" during transient network blips. These are
not real bad requests — they're gateway errors wearing a 400 mask.
Detect known gateway error phrases in 400 response bodies and mark
them as retryable so the existing exponential backoff handles them.
This commit is contained in:
TheArchitectit
2026-04-27 11:49:42 -05:00
parent d8c57ed317
commit 414a1aca4f
2 changed files with 31 additions and 0 deletions

View File

@@ -924,6 +924,22 @@ const fn is_retryable_status(status: reqwest::StatusCode) -> bool {
matches!(status.as_u16(), 408 | 409 | 429 | 500 | 502 | 503 | 504)
}
/// Some providers return HTTP 400 with an unparseable body when a gateway
/// or proxy flakes (e.g. "HTTP 400 from backend (no parseable body)").
/// These are transient network blips, not actual bad requests, and should
/// be retried. We detect them by checking the body for known gateway error
/// phrases.
fn is_retryable_400(status: reqwest::StatusCode, body: &str) -> bool {
if status != reqwest::StatusCode::BAD_REQUEST {
return false;
}
let lowered = body.to_ascii_lowercase();
lowered.contains("no parseable body")
|| lowered.contains("connection reset")
|| lowered.contains("broken pipe")
|| lowered.contains("empty reply from server")
}
/// Anthropic API keys (`sk-ant-*`) are accepted over the `x-api-key` header
/// and rejected with HTTP 401 "Invalid bearer token" when sent as a Bearer
/// token via `ANTHROPIC_AUTH_TOKEN`. This happens often enough in the wild

View File

@@ -1682,6 +1682,21 @@ const fn is_retryable_status(status: reqwest::StatusCode) -> bool {
matches!(status.as_u16(), 408 | 409 | 429 | 500 | 502 | 503 | 504)
}
/// Some providers return HTTP 400 with an unparseable body when a gateway
/// or proxy flakes (e.g. "HTTP 400 from backend (no parseable body)").
/// These are transient network blips, not actual bad requests, and should
/// be retried.
fn is_retryable_400(status: reqwest::StatusCode, body: &str) -> bool {
if status != reqwest::StatusCode::BAD_REQUEST {
return false;
}
let lowered = body.to_ascii_lowercase();
lowered.contains("no parseable body")
|| lowered.contains("connection reset")
|| lowered.contains("broken pipe")
|| lowered.contains("empty reply from server")
}
/// Generate a suggested user action based on the HTTP status code and error context.
/// This provides actionable guidance when API requests fail.
fn suggested_action_for_status(status: reqwest::StatusCode) -> Option<String> {