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

  1. Read the embedded message string: it is reqwest::Error's Display and names the failing phase (body decode, request, etc.)
  2. Enable the transport's debug logging (ReqwestTransport logs 'Request failed' with URL and status) to capture context the variant itself drops
  3. Retry idempotent requests with backoff: mid-body resets are frequently keep-alive races that succeed on a fresh connection
  4. 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

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


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