openai/codex · error · io::Error

network proxy attribution environment mismatch

Error message

network proxy attribution environment mismatch

What it means

The attribution token resolved to a registered execution, but the ingress was constructed with an expected environment id and the token's state carries a different one, so the connection is rejected with PermissionDenied. This is a deliberate isolation check in BindConnectionAttribution::serve: traffic attributed to an execution from another environment must not be routed through this ingress. It only fires when the bind supplied an environment_id AND the resolved state's environment_id is set and differs.

Source

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

    type Output = S::Output;
    type Error = BoxError;

    async fn serve(&self, mut stream: TcpStream) -> Result<Self::Output, Self::Error> {
        let state = match read_attribution_token(&mut stream).await? {
            Some(token) => self.state.for_execution_token(&token).ok_or_else(|| {
                io::Error::new(
                    io::ErrorKind::PermissionDenied,
                    "unknown network proxy attribution token",
                )
            })?,
            None => self.state.as_ref().clone(),
        };
        if let Some(expected_environment_id) = self.environment_id.as_deref()
            && state
                .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);

View on GitHub (pinned to 339751715c)

Solutions

  1. Use a token minted in the same environment the ingress was bound to -- re-export CODEX_NETWORK_PROXY_ATTRIBUTION inside that environment and reconnect.
  2. Verify the environment id passed at bind time matches the environment recorded for the token's execution in the registry.
  3. Restart bridge and proxy within one environment/session so both sides derive from the same configuration.

Example fix

// before: token exported in environment A, ingress bound to environment B
// $ export CODEX_NETWORK_PROXY_ATTRIBUTION=<token-from-env-A>
// after: obtain the token inside the environment the ingress serves
let token = env::var("CODEX_NETWORK_PROXY_ATTRIBUTION")?; // read within env B's execution
Defensive patterns

Strategy: validation

Validate before calling

// If both ids are visible to the client, compare before dialing
if let (Some(expected), Some(actual)) = (bind_environment_id, token_environment_id) {
    if expected != actual {
        return Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "attribution token belongs to a different environment",
        ));
    }
}

Try / catch

Match io::ErrorKind::PermissionDenied whose message contains 'environment mismatch': surface it as a configuration error (wrong environment for this token); do not retry -- retrying cannot change the environment.

Prevention

When it happens

Trigger: The ingress is built via BindConnectionAttribution::new(..., Some(expected_env)) while the client presents a token minted inside a different environment id -- e.g. a token exported in devcontainer A used against an ingress bound to devcontainer B.

Common situations: Copying CODEX_NETWORK_PROXY_ATTRIBUTION between shells or containers of different environments; environment/workspace renamed so bind config and token registry disagree; multi-environment test setups mixing tokens.

Related errors


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