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

  1. 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).
  2. If the chain is legitimate, resolve the final URL once yourself and send the request directly to that endpoint.
  3. Build the pool with RouteAwareClientPool::new_without_redirects(...) so 3xx responses are returned to you, then follow Location yourself with your own cap.
  4. 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(&current).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(&current).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

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


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/ae124b374e654f07. Report an issue: GitHub.