openai/codex · warning · io::Error

empty proxy connection

Error message

empty proxy connection

What it means

A TCP peer connected to the proxy ingress and closed without sending a single byte: the one-byte peek in read_attribution_token returned 0 (EOF). The ingress needs at least one byte to decide whether a connection is an attributed bridge connection (first byte of the magic) or plain passthrough, so an entirely empty connection is rejected with io::ErrorKind::UnexpectedEof instead of being forwarded.

Source

Thrown at codex-rs/network-proxy/src/attribution.rs:78

                .environment_id()
                .is_some_and(|actual| actual != expected_environment_id)
        {
            return Err(io::Error::new(
                io::ErrorKind::PermissionDenied,
                "network proxy attribution environment mismatch",
            )
            .into());
        }
        stream.extensions_mut().insert(Arc::new(state));
        self.inner.serve(stream).await.map_err(Into::into)
    }
}

async fn read_attribution_token(stream: &mut TcpStream) -> Result<Option<String>, BoxError> {
    let mut marker = [0_u8; 1];
    let read = stream.stream.peek(&mut marker).await?;
    if read == 0 {
        return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "empty proxy connection").into());
    }
    if marker[0] != ATTRIBUTION_FRAME_MAGIC[0] {
        return Ok(None);
    }

    let token = tokio::time::timeout(ATTRIBUTION_FRAME_TIMEOUT, async {
        let mut magic = [0_u8; ATTRIBUTION_FRAME_MAGIC.len()];
        stream.read_exact(&mut magic).await?;
        if &magic != ATTRIBUTION_FRAME_MAGIC {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "invalid network proxy attribution frame",
            ));
        }

        let token_len = stream.read_u16().await? as usize;
        if token_len == 0 || token_len > MAX_ATTRIBUTION_TOKEN_LEN {
            return Err(io::Error::new(

View on GitHub (pinned to 339751715c)

Solutions

  1. If it comes from a probe or scanner, expect it -- log at debug level and move on; point real health checks at an actual health endpoint.
  2. If it comes from your own bridge client, write the attribution frame (or the first payload byte) immediately after connect rather than dropping the socket.
  3. Do not gate retry or startup logic on this error; it carries no recoverable state.

Example fix

// before: health check connects, reads, closes -> server peeks 0 bytes
// after: send at least one byte, or target a real health endpoint
let mut s = TcpStream::connect(addr).await?;
s.write_all(b"PING\n").await?;
Defensive patterns

Strategy: try-catch

Try / catch

// Peer behavior cannot be validated in advance; classify at accept time
if let Some(e) = err.downcast_ref::<io::Error>() {
    if e.kind() == io::ErrorKind::UnexpectedEof
        && e.to_string().contains("empty proxy connection")
    {
        // probe or scanner: log at debug, do not retry, do not alert
    }
}

Prevention

When it happens

Trigger: Anything that opens a socket and immediately closes it: TCP health checks and port probes (nc -z, telnet, load-balancer checks), port scanners, or a bridge client that connects then crashes/exits before writing the preface.

Common situations: Pointing a generic TCP health check at the proxy port; security scanners sweeping the port; a client bug that connects 'early' and drops the socket before it has anything to write.

Related errors


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