Not able to use env var for otlp headers in config
Open 💬 2 comments Opened Mar 12, 2026 by flexdinesh
What version of Codex CLI is running?
0.114.0
What subscription do you have?
Pro
Which model were you using?
GPT-5.4
What platform is your computer?
Arch Linux
What terminal emulator and version are you using (if applicable)?
Ghostty
What issue are you seeing?
I am able to set up and use OTEL config. But I want to pass shell environment variables to the header value. This example in the doc does not work now.
[otel]
exporter = { otlp-http = {
endpoint = "https://otel.example.com/v1/logs",
protocol = "binary",
headers = { "x-otlp-api-key" = "${OTLP_TOKEN}" }
}}
It works if I hardcode the token value.
What steps can reproduce the bug?
Try to use environment variable in OTEL config headers
What is the expected behavior?
OTEL config headers should be able to interpolate the value from shell environment variables
Additional information
_No response_
2 Comments
Root Cause Analysis
I traced through the config and OTEL initialisation pipeline and found the exact location of the problem.
Where header values enter the pipeline:
codex-rs/core/src/config/types.rsdefinesOtelExporterKindwith a plainheaders: HashMap<String, String>field. Header values are stored verbatim from the TOML file — no interpolation step exists.Where headers are consumed:
codex-rs/core/src/otel_init.rsconverts theconfig.otelsettings into anOtelExportervalue via theto_otel_exporterclosure. Both theOtlpHttpandOtlpGrpcbranches simply clone header values straight through:What happens to
${OTLP_TOKEN}:The string
"${OTLP_TOKEN}"is passed unchanged all the way into the HTTP client header map and eventually sent as a literal header value, which is not a valid auth token.Proposed fix:
Add a small
expand_env_vars(value: &str) -> Stringhelper inotel_init.rsthat replaces${VAR_NAME}with the corresponding process environment variable. If the variable is not set the placeholder is left unchanged (rather than silently becoming an empty string) so misconfiguration is visible.Then change both map calls in
to_otel_exporterfrom.map(|(k, v)| (k.clone(), v.clone()))to.map(|(k, v)| (k.clone(), expand_env_vars(v))).This fix is entirely contained to
otel_init.rs. No config schema changes are needed — users continue usingheaders = { ... }exactly as in the issue example. The change also coversOtlpGrpcheaders and thetrace_exporter/metrics_exportervariants since they all go through the same closure.I've verified this compiles and added unit tests covering: substitution of a set variable, no-op for values with no placeholder, multiple placeholders in one value, an unset variable left unchanged, and an unclosed
${left unchanged.Happy to open a PR if that would be helpful.
Also observing the same; several rounds of trying to figure this out and it would appear that the docs are wrong and this does not work as expected.