openai/codex · error · anyhow::Error

expected exactly one of secret_env_var or secret_file

Error message

expected exactly one of secret_env_var or secret_file

What it means

Each inject_request_headers entry must declare exactly one secret source. compile_injected_header matches on the (secret_env_var, secret_file) pair: (Some, None) reads the environment variable, (None, Some) reads the file, and every other combination — both set, or neither — returns 'expected exactly one of secret_env_var or secret_file' under the context 'failed to compile injected header {name}'.

Source

Thrown at codex-rs/network-proxy/src/mitm_hook.rs:367

    FileFn: Fn(&AbsolutePathBuf) -> Result<String>,
{
    let name = parse_header_name(&header.name)?;
    let (secret, source) = match (
        header.secret_env_var.as_deref(),
        header.secret_file.as_deref(),
    ) {
        (Some(env_var), None) => {
            let value = resolve_env_var(env_var)
                .ok_or_else(|| anyhow!("missing required environment variable {env_var}"))?;
            (value, SecretSource::EnvVar(env_var.to_string()))
        }
        (None, Some(secret_file)) => {
            let path = parse_secret_file(secret_file)?;
            let value = read_secret_file(&path)?;
            (value, SecretSource::File(path))
        }
        _ => {
            return Err(anyhow!(
                "expected exactly one of secret_env_var or secret_file"
            ));
        }
    };

    let prefix = header.prefix.clone().unwrap_or_default();
    let value = HeaderValue::from_str(&format!("{prefix}{secret}"))
        .with_context(|| format!("invalid value for injected header {}", header.name))?;

    Ok(ResolvedInjectedHeader {
        name,
        value,
        source,
    })
}

fn hook_matches(hook: &MitmHook, req: &Request) -> bool {
    let method = req.method().as_str().to_ascii_uppercase();

View on GitHub (pinned to 339751715c)

Solutions

  1. Keep exactly one of secret_env_var or secret_file per entry — prefer the file for services, the env var for interactive runs
  2. If the header needs no secret at all, remove the entire inject_request_headers entry
  3. Run validate_mitm_hook_config after editing config so the failure surfaces before proxy start

Example fix

# before — both sources
[[network.mitm_hooks.actions.inject_request_headers]]
name = "authorization"
secret_env_var = "MY_TOKEN"
secret_file = "/etc/codex/secrets/my_token"

# after — exactly one source
[[network.mitm_hooks.actions.inject_request_headers]]
name = "authorization"
secret_env_var = "MY_TOKEN"
Defensive patterns

Strategy: validation

Validate before calling

// Rust — exactly one source per injected header
for hook in &config.mitm_hooks {
    for h in &hook.actions.inject_request_headers {
        if h.secret_env_var.is_some() == h.secret_file.is_some() {
            return Err(anyhow!("header {} must set exactly one of secret_env_var or secret_file", h.name));
        }
    }
}

Type guard

fn has_single_secret_source(h: &InjectedHeaderConfig) -> bool {
    h.secret_env_var.is_some() != h.secret_file.is_some()
}

Try / catch

match compile_mitm_hooks(&config) {
    Ok(_) => {}
    Err(err) => eprintln!("{err:#}"), // names the header and the rule that failed
}

Prevention

When it happens

Trigger: A header entry with both keys set ({ secret_env_var = "MY_TOKEN", secret_file = "/etc/codex/secrets/token" }) or with neither key present; raised by compile_mitm_hooks_with_resolvers at hook compilation.

Common situations: Copy-pasting an example and adding the second source 'for safety'; migrating from env var to file and leaving both keys; commenting out one key and deleting the other, leaving the entry secret-less.

Related errors


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