{"record":{"id":"ae124b374e654f07","repo":"openai/codex","slug":"too-many-redirects","errorCode":null,"errorMessage":"too many redirects","messagePattern":"too many redirects","errorType":"exception","errorClass":"RouteAwareRequestError","httpStatus":null,"severity":"error","filePath":"codex-rs/http-client/src/route_aware_client_pool.rs","lineNumber":98,"sourceCode":"pub enum RouteAwareClientPoolError {\n    #[error(\"failed to resolve the outbound proxy route: {0}\")]\n    Resolve(#[source] io::Error),\n    #[error(transparent)]\n    Build(#[from] BuildRouteAwareHttpClientError),\n}\n\n/// Error returned while building, routing, or sending a route-aware request.\n#[derive(Debug, thiserror::Error)]\npub enum RouteAwareRequestError {\n    #[error(transparent)]\n    Request(#[from] reqwest::Error),\n    #[error(transparent)]\n    Route(#[from] RouteAwareClientPoolError),\n    #[error(\"failed to build route-aware request: {0}\")]\n    Build(String),\n    #[error(\"redirect target uses unsupported URL scheme: {0}\")]\n    UnsupportedRedirectScheme(String),\n    #[error(\"too many redirects\")]\n    TooManyRedirects,\n    #[error(\"route-aware request timed out\")]\n    Timeout,\n}\n\nimpl RouteAwareRequestError {\n    /// Classifies transport, proxy, and certificate failures without exposing request details.\n    pub fn failure_class(&self) -> Option<RouteFailureClass> {\n        if self.is_timeout() {\n            return Some(RouteFailureClass::ConnectTimeout);\n        }\n        if self.status() == Some(StatusCode::PROXY_AUTHENTICATION_REQUIRED) {\n            return Some(RouteFailureClass::ProxyAuthenticationRequired);\n        }\n        if let Self::Route(RouteAwareClientPoolError::Resolve(error)) = self\n            && let Some(source) = error.get_ref()\n            && source.is::<rustls::Error>()\n        {","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/http-client/src/route_aware_client_pool.rs#L80-L116","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: pool follows redirects itself and errors after 10 hops\nlet pool = RouteAwareClientPool::new(factory, route_class);\nlet resp = pool.get(url).send().await; // Err(RouteAwareRequestError::TooManyRedirects)\n\n// after: receive 3xx responses and follow Location manually with your own limit\nlet pool = RouteAwareClientPool::new_without_redirects(factory, route_class);\nlet mut current = initial_url;\nfor _ in 0..MAX_HOPS {\n    let resp = pool.get(&current).send().await?;\n    if !resp.status().is_redirection() {\n        return Ok(resp);\n    }\n    let loc = resp.headers().get(LOCATION).and_then(|v| v.to_str().ok());\n    let Some(loc) = loc else { return Ok(resp); };\n    current = resp.url().join(loc)?;\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight: count hops with a no-redirect probe before the real request\nlet probe = RouteAwareClientPool::new_without_redirects(factory.clone(), route_class);\nlet mut current = url.clone();\nfor _ in 0..10 {\n    let resp = probe.get(&current).send().await?;\n    if !resp.status().is_redirection() {\n        break; // chain fits within the pool's cap\n    }\n    let loc = resp.headers().get(LOCATION).and_then(|v| v.to_str().ok());\n    let Some(loc) = loc else { break; };\n    current = resp.url().join(loc)?;\n}","typeGuard":"fn is_too_many_redirects(e: &RouteAwareRequestError) -> bool {\n    matches!(e, RouteAwareRequestError::TooManyRedirects)\n}","tryCatchPattern":"match result {\n    Err(RouteAwareRequestError::TooManyRedirects) => { /* surface the original URL and stop following; do not blind-retry */ }\n    Err(e) if e.is_timeout() => { /* retry with backoff and a fresh deadline */ }\n    other => other,\n}","preventionTips":["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."],"tags":["http","redirects","network","rust"],"backgroundTag":"too-many-redirects","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}