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
- Keep exactly one of secret_env_var or secret_file per entry — prefer the file for services, the env var for interactive runs
- If the header needs no secret at all, remove the entire inject_request_headers entry
- 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
- Pick one secret source per header entry and delete the other key
- Treat secret-source drift as a review blocker in config PRs
- Run validate_mitm_hook_config in CI on every config change
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
- network.mitm_hooks[{hook_index}].match.body is reserved for
- network.mitm_hooks[{hook_index}].host must not be empty
- missing required environment variable {env_var}
- path_prefixes must not contain empty entries
- glob pattern must not be empty
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/2118e9c6c8bdbf2b.
Report an issue: GitHub.