OAuth fallback silently uses hardcoded "dummy" API key after network change, causing 401

Open 💬 4 comments Opened Aug 6, 2026 by xluo233

Bug Description

When using Codex CLI with ChatGPT OAuth authentication (preferred_auth_method = "chatgpt"), switching networks (e.g., WiFi → mobile hotspot, VPN toggle) causes the OAuth token to expire. Instead of prompting the user to re-authenticate, Codex silently falls back to a hardcoded "dummy" API key and sends it to the OpenAI API, resulting in:

[System Error] unexpected status 401 Unauthorized: Incorrect API key provided: dummy.

This affects both interactive Codex sessions and codex exec, as well as any third-party tool that spawns codex app-server.

Steps to Reproduce

  1. Log in via codex login using ChatGPT OAuth
  2. Start a Codex session, confirm it works
  3. Switch to a different network (different WiFi, VPN, hotspot, etc.)
  4. Continue or resume the session — or simply run codex exec "say hello"
  5. Codex sends requests with "dummy" as the API key → 401

Reproduction Log

$ codex exec "say hello"
OpenAI Codex v0.145.0
provider: openai
model: gpt-5.6-sol

ERROR codex_api::endpoint::responses_websocket: failed to connect to websocket: HTTP error: 401 Unauthorized, url: wss://api.openai.com/v1/responses
ERROR: Reconnecting... 1/5
...
ERROR: Reconnecting... 5/5
warning: Falling back from WebSockets to HTTPS transport. unexpected status 401 Unauthorized: Incorrect API key provided: dummy. You can find your API key at https://platform.openai.com/account/api-keys., url: wss://api.openai.com/v1/responses

Expected Behavior

When the OAuth token cannot be refreshed, Codex should prompt the user to re-authenticate, not silently fall back to a dummy key.

Actual Behavior

Codex falls back through this chain:

  1. Try OAuth token refresh → fails (network changed, session stale)
  2. Try keychain → encryption key present but OAuth credentials stale
  3. Try ~/.codex/auth.json → contains {"auth_mode": "apikey", "OPENAI_API_KEY": "dummy"}
  4. If auth.json is deleted → binary has hardcoded "dummy" as ultimate fallback
  5. Request sent with Authorization: Bearer dummy → 401

What I've Tried

  • Deleting ~/.codex/auth.json — Codex uses hardcoded "dummy" fallback in the binary
  • Setting cli_auth_credentials_store = "keyring" in config.toml — dummy still used
  • Only codex logout && codex login resolves the issue, but must be done after every network change

Environment

  • macOS 15 (Darwin 24.2.0, arm64)
  • Codex CLI v0.145.0 (installed via Homebrew)
  • preferred_auth_method = "chatgpt" in ~/.codex/config.toml

Related Issues

  • #20871 — auth.json not reliably updated by codex login
  • #15151 — silent fallback causing misleading 401
  • #31459 — revoked OAuth token still used after logout/login
  • #5212 — OPENAI_API_KEY vs auth.json handling

Suggested Fix

When all auth methods fail, Codex should surface a clear error like "OAuth session expired. Run codex login to re-authenticate." instead of silently using "dummy" as the API key.

View original on GitHub ↗

4 Comments

ded-furby · 22 days ago

Thanks for the detailed report. Could you confirm whether ~/.codex/auth.json is regenerated after a logout/login cycle in this failure path, and whether codex config still shows preferred_auth_method as chatgpt after the network switch? A single debug run with CODEX_LOG=debug and one codex exec "say hello" (with secrets redacted) would help confirm whether the final fallback happens before or after token refresh failure. Please include your exact macOS build channel and version if possible.

ded-furby · 22 days ago

Could you share the exact value of OPENAI_API_KEY in your environment when this happens, and whether ~/.codex/auth.json is regenerated if you run codex logout then codex login without network changes? I can then suggest a minimal guard to avoid dummy-key fallback during network transitions.

xluo233 · 22 days ago

1. OPENAI_API_KEY in environment

OPENAI_API_KEY is not set in the environment at the time of failure. No other OpenAI-related env vars are present either.

2. Does auth.json regenerate after codex logout && codex login?

No. After logout/login (without network changes), auth.json is not recreated. Credentials are stored only in the macOS Keychain under service "Codex Auth".

3. Debug log

I’ve re-logged in and the issue isn’t currently reproducing. I’ll attach a full CODEX_LOG=debug log next time it occurs.

4. Environment

  • macOS 15.2 (Build 24C101), arm64
  • Codex CLI v0.145.0 (Homebrew)
  • preferred_auth_method = "chatgpt"
  • cli_auth_credentials_store = "keyring" (added while debugging, didn't help)
  • OPENAI_API_KEY is unset
xluo233 · 21 days ago

Update: Reproduced the failure and captured RUST_LOG=debug output. The auth flow reveals two compounding issues:

Successful run (before network change)

keyring: get password from entry MacCredential { service: "Codex Auth", account: "<redacted>" }
models cache: cache hit (within 300s TTL)
codex.conversation_starts auth_mode="Chatgpt"
codex.api_request status=200 auth.mode="Chatgpt"

Failed run (after network change)

keyring: get password from entry MacCredential { service: "Codex Auth", account: "<redacted>" }
models cache: cache is stale
models cache: no usable cache entry
codex.conversation_starts auth_mode="ApiKey"    ← should be "Chatgpt"

codex.websocket_connect status=401 auth.mode="ApiKey"
codex.auth_recovery auth.outcome="recovery_not_run"
  auth.recovery_reason="not_chatgpt_auth"       ← recovery skipped

What's happening

After a network change, the models cache (300s TTL) expires and Codex needs to re-fetch from chatgpt.com. The OAuth access token is likely expired and the refresh fails silently on the new network. At this point:

  1. The OAuth refresh failure causes a silent downgrade from auth_mode="Chatgpt" to auth_mode="ApiKey", substituting the hardcoded "dummy" as the API key. No error is shown to the user.
  2. When the 401 occurs, auth recovery checks auth_mode, sees "ApiKey", concludes "not_chatgpt_auth", and skips token refresh entirely. The original ChatGPT OAuth context is lost, so recovery can never kick in.