openai/codex · error · TransportError
network error: {0}
Error message
network error: {0} What it means
TransportError::Network is the catch-all branch of ReqwestTransport::map_error (transport.rs:86): any reqwest failure that is neither is_connect() nor is_timeout() is stringified into this variant. Typical contents are mid-body failures (connection reset after headers, invalid chunked transfer encoding, truncated body) and reqwest request/body errors. Unlike Connection, the original reqwest error is not kept as a source, only its Display string.
Source
Thrown at codex-rs/http-client/src/error.rs:23
use http::StatusCode;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum TransportError {
#[error("http {status}: {body:?}")]
Http {
status: StatusCode,
url: Option<String>,
headers: Option<HeaderMap>,
body: Option<String>,
},
#[error("retry limit reached")]
RetryLimit,
#[error("timeout")]
Timeout,
#[error("connection failed: {0}")]
Connection(#[source] HttpError),
#[error("network error: {0}")]
Network(String),
#[error("request build error: {0}")]
Build(String),
}
#[derive(Debug, Error)]
pub enum StreamError {
#[error("stream failed: {0}")]
Stream(String),
#[error("timeout")]
Timeout,
}
View on GitHub (pinned to 339751715c)
Solutions
- Read the embedded message string: it is reqwest::Error's Display and names the failing phase (body decode, request, etc.)
- Enable the transport's debug logging (ReqwestTransport logs 'Request failed' with URL and status) to capture context the variant itself drops
- Retry idempotent requests with backoff: mid-body resets are frequently keep-alive races that succeed on a fresh connection
- If it recurs on one endpoint only, capture the exchange through a debugging proxy to see where the body is truncated
Example fix
// before let resp = transport.execute(req).await?; // after: treat Network as transient for idempotent requests let resp = retry_with_backoff(3, || transport.execute(req.clone())).await?;
Defensive patterns
Strategy: retry
Type guard
fn is_network_error(e: &TransportError) -> bool {
matches!(e, TransportError::Network(_))
} Try / catch
match transport.execute(req).await {
Err(e @ TransportError::Network(msg)) => {
tracing::warn!(error = %msg, "network failure");
retry_if_idempotent(req, &e).await
}
other => other,
} Prevention
- Distinguish Network from Connection and Timeout first: it is the residual class and needs the message string to diagnose
- Retry only idempotent methods; mid-body failures can duplicate side effects otherwise
- Log the embedded reqwest Display string alongside your own request id to keep diagnosis possible
- Reuse connections aggressively but bound pool idle lifetime to avoid stale keep-alive resets
When it happens
Trigger: builder.send() or resp.bytes() fails with a reqwest error that is neither connect nor timeout: connection reset while reading the response body, broken chunked framing, Content-Length mismatch where the peer closes early, or request errors raised inside reqwest during send.
Common situations: Proxies or load balancers killing keep-alive connections mid-response, servers closing connections before the body completes, VPN or NAT idle reaping, response buffering middleboxes that truncate large payloads.
Related errors
- timeout
- too many redirects
- 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/b262ba88f626a9c3.
Report an issue: GitHub.