{"record":{"id":"98fce25b9ce21640","repo":"openai/codex","slug":"route-aware-request-timed-out","errorCode":null,"errorMessage":"route-aware request timed out","messagePattern":"route-aware request timed out","errorType":"exception","errorClass":"RouteAwareRequestError","httpStatus":null,"severity":"error","filePath":"codex-rs/http-client/src/route_aware_client_pool.rs","lineNumber":100,"sourceCode":"    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        {\n            return Some(RouteFailureClass::TlsError);\n        }","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/http-client/src/route_aware_client_pool.rs#L82-L118","documentation":"The builder's timeout(Duration) sets a whole-request budget that starts before outbound-route resolution and covers proxy/PAC route resolution, pooled-client construction, connection establishment, sending, awaiting the response, every redirect hop, and any rustls fallback retry (doc on RouteAwareRequestBuilder::timeout at route_aware_client_pool.rs:253-258). When any phase misses the deadline (tokio::time::timeout_at) or the deadline has already elapsed between hops, the pool returns RouteAwareRequestError::Timeout rather than a reqwest error.","triggerScenarios":"Calling .timeout(d) on the builder and awaiting send() when: proxy-route resolution (PAC script, WPAD, system proxy) hangs; TLS handshake or TCP connect is slow; the server responds slowly or the body transfer is large; redirect chains consume the budget across hops (remaining time is recomputed per hop and returned as Timeout once it hits zero).","commonSituations":"Timeout tuned too tight for large uploads/downloads; corporate PAC endpoints that block; misconfigured or unreachable system proxy; retrying with a deadline that is already spent; confusing the per-request .timeout() with with_connect_timeout(), which only bounds connection establishment.","solutions":["Raise the .timeout() value so the whole operation (headers, body transfer, and all redirect hops) fits inside the budget.","If only connection setup should be bounded, use RouteAwareClientPool::with_connect_timeout(...) or HttpClientBuilder::connect_timeout instead of the per-request timeout.","Diagnose the slow phase: DNS, PAC/WPAD resolution, proxy reachability, server latency.","Treat as transient: check is_timeout() and retry with a fresh deadline and backoff."],"exampleFix":"// before: whole-request budget too small for a large download\nlet resp = pool.get(url).timeout(Duration::from_secs(5)).send().await; // Err(RouteAwareRequestError::Timeout)\n\n// after: bound only connection establishment; do not cap the body transfer\nlet pool = RouteAwareClientPool::with_connect_timeout(factory, route_class, Duration::from_secs(10));\nlet resp = pool.get(url).send().await?;","handlingStrategy":"retry","validationCode":"// Before the real request, confirm the route/proxy layer answers within a small budget\nlet probe = pool.head(&health_url).timeout(Duration::from_secs(2)).send().await;\nif probe.is_err() {\n    // fix proxy/network configuration before issuing the expensive request\n}","typeGuard":"fn is_request_timeout(e: &RouteAwareRequestError) -> bool { e.is_timeout() }","tryCatchPattern":"if let Err(e) = &result {\n    if e.is_timeout() {\n        // backoff, then rebuild the request so timeout_mut() gets a fresh Duration\n    } else {\n        return Err(result.err().unwrap());\n    }\n}","preventionTips":["Budget the timeout for the total operation: route resolution + connect + headers + body + redirects.","Use with_connect_timeout when only slow connects are the concern.","Never reuse an elapsed deadline across retries; construct a new request each attempt."],"tags":["http","timeout","network","rust"],"backgroundTag":"request-timeout","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}