Summary
src/codex/inject.ts still emits the legacy env_http_headers = { "x-opencodex-api-key" = "OPENCODEX_API_AUTH_TOKEN" } line when injecting the opencodex provider into $CODEX_HOME/config.toml. Since codex-cli 0.146 the runtime contract for custom model providers is env_key = "<VAR>" (the proxy must read the API key from that environment variable), and env_http_headers with a dedicated x-opencodex-api-key header is no longer the preferred transport.
This is the generator side of the auth contract already fixed server-side in #1686: #1686 made /v1/responses accept an Authorization: Bearer admission token (what env_key produces), but the injector still writes the old header form, so fresh installs do not benefit from the modern contract.
Concretely, the divergence is visible today:
| Source |
Form emitted |
src/codex/inject.ts (current main) |
env_http_headers = { "x-opencodex-api-key" = "OPENCODEX_API_AUTH_TOKEN" } |
src/grok/* injector (per #1686 body) |
env_key = "..." |
| codex-cli 0.146+ documented contract |
env_key = "<VAR>" |
Reproduction
# 1. Install opencodex, start the proxy, run any sync that injects the provider:
ocx start # or: ocx sync
# 2. Inspect the generated provider block:
grep -n "env_key\|env_http_headers" "$CODEX_HOME/config.toml"
# → env_http_headers = { "x-opencodex-api-key" = "OPENCODEX_API_AUTH_TOKEN" }
# (legacy form — the modern runtime prefers env_key)
# 3. Run the CLI through the proxy:
codex exec --skip-git-repo-check -m cmd/deepseek/deepseek-v4-flash "responda apenas: OK"
# → works only because the operator manually edited config.toml to env_key,
# or because an older injector left env_key behind.
Root cause
src/codex/inject.ts builds the provider TOML block:
if (includeApiAuthHeader) {
lines.push(
'env_http_headers = { "x-opencodex-api-key" = "OPENCODEX_API_AUTH_TOKEN" }',
);
}
This string predates the codex-cli 0.146 env_key contract. The server-side admission (fixed in #1686) reads Authorization: Bearer and x-opencodex-api-key; the header form still works when the runtime forwards it, but the modern, documented, and already-server-supported form is env_key, and the two injectors are inconsistent.
Proposed diff
--- a/src/codex/inject.ts
+++ b/src/codex/inject.ts
@@ -228,7 +228,7 @@ export function buildOcxProviderBlock(
if (includeApiAuthHeader) {
lines.push(
- 'env_http_headers = { "x-opencodex-api-key" = "OPENCODEX_API_AUTH_TOKEN" }',
+ 'env_key = "OPENCODEX_API_AUTH_TOKEN"',
);
}
if (supportsWebsockets) lines.push("supports_websockets = true");
Alternatives considered
- Keep
env_http_headers and rely on x-opencodex-api-key admission — works today, but keeps two incompatible auth transports for the same proxy, and the runtime may drop env_http_headers support in a future release (the header name is not part of the documented 0.146+ provider contract).
- Emit both lines — redundant; the proxy treats either as admission, and the extra header is noise.
- Make it configurable (
injectAuthForm = "env_key" | "env_http_headers") — unnecessary until a runtime actually rejects one of them; the modern contract is env_key.
Test matrix
| Case |
Expected |
Fresh ocx start / ocx sync on codex-cli 0.146+ |
config.toml contains env_key = "OPENCODEX_API_AUTH_TOKEN" |
codex exec -m <routed-model> after injection |
200, no 401 |
/v1/responses with Authorization: Bearer $OPENCODEX_API_AUTH_TOKEN |
200 (server side already accepts — #1686) |
Upgrade path: existing config with env_http_headers |
proxy still admits via x-opencodex-api-key (back-compat unchanged) |
| Claude Code / Grok injectors |
unchanged (env_key already emitted there) |
Summary
src/codex/inject.tsstill emits the legacyenv_http_headers = { "x-opencodex-api-key" = "OPENCODEX_API_AUTH_TOKEN" }line when injecting the opencodex provider into$CODEX_HOME/config.toml. Since codex-cli 0.146 the runtime contract for custom model providers isenv_key = "<VAR>"(the proxy must read the API key from that environment variable), andenv_http_headerswith a dedicatedx-opencodex-api-keyheader is no longer the preferred transport.This is the generator side of the auth contract already fixed server-side in #1686: #1686 made
/v1/responsesaccept anAuthorization: Beareradmission token (whatenv_keyproduces), but the injector still writes the old header form, so fresh installs do not benefit from the modern contract.Concretely, the divergence is visible today:
src/codex/inject.ts(current main)env_http_headers = { "x-opencodex-api-key" = "OPENCODEX_API_AUTH_TOKEN" }src/grok/*injector (per #1686 body)env_key = "..."env_key = "<VAR>"Reproduction
Root cause
src/codex/inject.tsbuilds the provider TOML block:This string predates the codex-cli 0.146
env_keycontract. The server-side admission (fixed in #1686) readsAuthorization: Bearerandx-opencodex-api-key; the header form still works when the runtime forwards it, but the modern, documented, and already-server-supported form isenv_key, and the two injectors are inconsistent.Proposed diff
Alternatives considered
env_http_headersand rely onx-opencodex-api-keyadmission — works today, but keeps two incompatible auth transports for the same proxy, and the runtime may dropenv_http_headerssupport in a future release (the header name is not part of the documented 0.146+ provider contract).injectAuthForm = "env_key" | "env_http_headers") — unnecessary until a runtime actually rejects one of them; the modern contract isenv_key.Test matrix
ocx start/ocx syncon codex-cli 0.146+config.tomlcontainsenv_key = "OPENCODEX_API_AUTH_TOKEN"codex exec -m <routed-model>after injection/v1/responseswithAuthorization: Bearer $OPENCODEX_API_AUTH_TOKENenv_http_headersx-opencodex-api-key(back-compat unchanged)env_keyalready emitted there)