Summary
When codexapp runs without usable Codex OAuth tokens but ~/.codex/config.toml explicitly selects a custom/model provider, it still synthesizes the unauthenticated OpenCode Zen fallback. That starts codex app-server with model_provider="opencode_zen", overriding the user configured provider.
In my setup, config.toml selects Azure, but requests were routed through the local OpenCode Zen proxy until I created a disabled provider-state file.
Reproduction
- Configure
~/.codex/config.toml with an explicit provider, for example:
model = "gpt-5.5"
model_provider = "azure"
[model_providers.azure]
# Azure provider config here
- Have no
~/.codex/webui-custom-providers.json file.
- Have no Codex OAuth auth file, or have an
auth.json that does not contain tokens.access_token. My local auth.json only had top-level provider API key names, not tokens.*.
- Start
codexapp, for example:
npx codexapp --no-password --no-login
- Inspect the spawned
codex app-server process args or send a request.
Actual behavior
The spawned app-server receives OpenCode Zen as the active provider override, e.g.:
-c model="big-pickle"
-c model_provider="opencode_zen"
-c model_providers.opencode_zen.base_url="http://127.0.0.1:<port>/codex-api/zen-proxy/v1"
-c model_providers.opencode_zen.wire_api="responses"
That overrides the explicit model_provider = "azure" in config.toml, so traffic goes through the Zen proxy path.
Expected behavior
If ~/.codex/config.toml explicitly sets model_provider, codexapp should respect that provider and should not synthesize the unauthenticated OpenCode Zen fallback. The fallback should only apply to truly unconfigured/no-auth setups.
Code path observed
The behavior appears to come from this path:
buildAppServerConfig() calls ensureDefaultFreeModeStateForMissingAuthSync(...).
hasUsableCodexAuthSync() only treats auth.tokens.access_token as usable auth.
- With no provider-state file and no usable Codex OAuth token,
shouldCreateDefaultFreeModeStateForMissingAuth(null, false) returns true.
createDefaultOpenCodeZenFreeModeState() returns the Zen fallback state.
getFreeModeConfigArgs() adds model_provider="opencode_zen".
Workaround
Creating this file disables the fallback and lets config.toml win again:
{"enabled":false,"apiKey":null,"model":"openrouter/free","provider":"openrouter","wireApi":"responses"}
at:
~/.codex/webui-custom-providers.json
Summary
When
codexappruns without usable Codex OAuth tokens but~/.codex/config.tomlexplicitly selects a custom/model provider, it still synthesizes the unauthenticated OpenCode Zen fallback. That startscodex app-serverwithmodel_provider="opencode_zen", overriding the user configured provider.In my setup,
config.tomlselects Azure, but requests were routed through the local OpenCode Zen proxy until I created a disabled provider-state file.Reproduction
~/.codex/config.tomlwith an explicit provider, for example:~/.codex/webui-custom-providers.jsonfile.auth.jsonthat does not containtokens.access_token. My localauth.jsononly had top-level provider API key names, nottokens.*.codexapp, for example:codex app-serverprocess args or send a request.Actual behavior
The spawned app-server receives OpenCode Zen as the active provider override, e.g.:
That overrides the explicit
model_provider = "azure"inconfig.toml, so traffic goes through the Zen proxy path.Expected behavior
If
~/.codex/config.tomlexplicitly setsmodel_provider,codexappshould respect that provider and should not synthesize the unauthenticated OpenCode Zen fallback. The fallback should only apply to truly unconfigured/no-auth setups.Code path observed
The behavior appears to come from this path:
buildAppServerConfig()callsensureDefaultFreeModeStateForMissingAuthSync(...).hasUsableCodexAuthSync()only treatsauth.tokens.access_tokenas usable auth.shouldCreateDefaultFreeModeStateForMissingAuth(null, false)returns true.createDefaultOpenCodeZenFreeModeState()returns the Zen fallback state.getFreeModeConfigArgs()addsmodel_provider="opencode_zen".Workaround
Creating this file disables the fallback and lets
config.tomlwin again:{"enabled":false,"apiKey":null,"model":"openrouter/free","provider":"openrouter","wireApi":"responses"}at: