openai/codex · error · RouteAwareRequestError
too many redirects
Error message
too many redirects
What it means
RouteAwareClientPool disables reqwest-internal redirects whenever the outbound proxy policy is RespectSystemProxy or TLS-backend fallback is enabled, and follows each 3xx hop itself so every hop gets a fresh proxy-route decision. The manual chain is capped at MAX_REDIRECTS = 10 hops (route_aware_redirect.rs:31). When the pool is about to follow an 11th redirect, send() returns RouteAwareRequestError::TooManyRedirects instead of continuing.
Source
Thrown at codex-rs/http-client/src/route_aware_client_pool.rs:98
pub enum RouteAwareClientPoolError {
#[error("failed to resolve the outbound proxy route: {0}")]
Resolve(#[source] io::Error),
#[error(transparent)]
Build(#[from] BuildRouteAwareHttpClientError),
}
/// Error returned while building, routing, or sending a route-aware request.
#[derive(Debug, thiserror::Error)]
pub enum RouteAwareRequestError {
#[error(transparent)]
Request(#[from] reqwest::Error),
#[error(transparent)]
Route(#[from] RouteAwareClientPoolError),
#[error("failed to build route-aware request: {0}")]
Build(String),
#[error("redirect target uses unsupported URL scheme: {0}")]
UnsupportedRedirectScheme(String),
#[error("too many redirects")]
TooManyRedirects,
#[error("route-aware request timed out")]
Timeout,
}
impl RouteAwareRequestError {
/// Classifies transport, proxy, and certificate failures without exposing request details.
pub fn failure_class(&self) -> Option<RouteFailureClass> {
if self.is_timeout() {
return Some(RouteFailureClass::ConnectTimeout);
}
if self.status() == Some(StatusCode::PROXY_AUTHENTICATION_REQUIRED) {
return Some(RouteFailureClass::ProxyAuthenticationRequired);
}
if let Self::Route(RouteAwareClientPoolError::Resolve(error)) = self
&& let Some(source) = error.get_ref()
&& source.is::<rustls::Error>()
{View on GitHub (pinned to 339751715c)
Solutions
- Reproduce with curl -I and count Location hops; fix the redirect cycle or over-long chain on the server side (a loop is the most common root cause).
- If the chain is legitimate, resolve the final URL once yourself and send the request directly to that endpoint.
- Build the pool with RouteAwareClientPool::new_without_redirects(...) so 3xx responses are returned to you, then follow Location yourself with your own cap.
- If you control the destination service, collapse http->https and host canonicalization into a single redirect to stay under 10 hops.
Example fix
// before: pool follows redirects itself and errors after 10 hops
let pool = RouteAwareClientPool::new(factory, route_class);
let resp = pool.get(url).send().await; // Err(RouteAwareRequestError::TooManyRedirects)
// after: receive 3xx responses and follow Location manually with your own limit
let pool = RouteAwareClientPool::new_without_redirects(factory, route_class);
let mut current = initial_url;
for _ in 0..MAX_HOPS {
let resp = pool.get(¤t).send().await?;
if !resp.status().is_redirection() {
return Ok(resp);
}
let loc = resp.headers().get(LOCATION).and_then(|v| v.to_str().ok());
let Some(loc) = loc else { return Ok(resp); };
current = resp.url().join(loc)?;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: count hops with a no-redirect probe before the real request
let probe = RouteAwareClientPool::new_without_redirects(factory.clone(), route_class);
let mut current = url.clone();
for _ in 0..10 {
let resp = probe.get(¤t).send().await?;
if !resp.status().is_redirection() {
break; // chain fits within the pool's cap
}
let loc = resp.headers().get(LOCATION).and_then(|v| v.to_str().ok());
let Some(loc) = loc else { break; };
current = resp.url().join(loc)?;
} Type guard
fn is_too_many_redirects(e: &RouteAwareRequestError) -> bool {
matches!(e, RouteAwareRequestError::TooManyRedirects)
} Try / catch
match result {
Err(RouteAwareRequestError::TooManyRedirects) => { /* surface the original URL and stop following; do not blind-retry */ }
Err(e) if e.is_timeout() => { /* retry with backoff and a fresh deadline */ }
other => other,
} Prevention
- Store final, canonical URLs in configuration instead of relying on long redirect chains.
- Keep a no-redirect pool available for debugging: it shows each 3xx hop and its Location header.
- Remember the 10-hop cap applies to manual-follow pools (RespectSystemProxy or rustls fallback); do not assume reqwest defaults apply.
When it happens
Trigger: Awaiting RouteAwareRequestBuilder::send() (built via get/post/put/delete/request) against a URL whose 3xx chain exceeds 10 hops: a redirect loop (A->B->A), a server redirecting a URL to itself, or a legitimate chain longer than 10 (SSO login portals, CDN and host-canonicalization hops). Only fires when the pool follows redirects manually (RespectSystemProxy policy or with_tls_backend_fallback); under a plain reqwest-default pool the equivalent surfaces as a reqwest redirect policy error instead.
Common situations: Corporate PAC/system-proxy environments that force RespectSystemProxy; auth portals that bounce between hosts; stale URLs after a service moved; trailing-slash vs non-slash redirect loops; http->https combined with host canonicalization multiplying hops.
Related errors
- timeout
- network error: {0}
- route-aware request timed out
- standalone Codex updater request failed with status {status}
- Luna Responses WebSocket connection timed out
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/ae124b374e654f07.
Report an issue: GitHub.