Hmbown/CodeWhale · error
OpenAI Codex uses OAuth. Sign in with ChatGPT via…
Error message
OpenAI Codex uses OAuth. Sign in with ChatGPT via `codewhale auth chatgpt` (subscription billing, Codewhale-owned tokens). The openai API-key route is a different billing owner. Alternatively run `codex login`, then grant exact read-only access with `codewhale auth external-consent --provider openai-codex --mode read-only`, or set OPENAI_CODEX_ACCESS_TOKEN for this process; Codewhale does not store an API key for this provider.
What it means
The credential-save path refuses to store an API key for the `openai-codex` provider because Codex is OAuth-only: billing runs through a ChatGPT subscription with Codewhale-owned tokens, not a user API key. The error explains the supported sign-in routes instead of persisting a key that the provider would never use.
Solutions
- Run `codewhale auth chatgpt` to sign in with ChatGPT OAuth (subscription billing)
- Or run `codex login`, then `codewhale auth external-consent --provider openai-codex --mode read-only`
- Or set the `OPENAI_CODEX_ACCESS_TOKEN` env var for this process only (Codewhale does not store it)
Example fix
// before codewhale auth save --provider openai-codex --api-key sk-... // after codewhale auth chatgpt
Defensive patterns
Strategy: try-catch
Validate before calling
if provider == ApiProvider::OpenaiCodex {
eprintln!("openai-codex is OAuth-only; use `codewhale auth chatgpt`");
} else { save_api_key(...)?; } Type guard
fn supports_api_key(p: ApiProvider) -> bool { p != ApiProvider::OpenaiCodex } Try / catch
match save_result {
Err(e) if e.to_string().starts_with("OpenAI Codex uses OAuth") => run_chatgpt_login_flow(),
other => other?,
} Prevention
- Never attempt to store API keys for OAuth-only providers
- Route openai-codex users straight to the ChatGPT login flow
- Check provider metadata before generic key-save automation
When it happens
Trigger: Calling the provider credential save routine with `identity.provider == ApiProvider::OpenaiCodex` and any API key.
Common situations: User pastes an OpenAI platform API key expecting it to work with Codex; scripts that generically save keys for every provider; migrating configs from the plain `openai` provider to `openai-codex`.
Related errors
- bearer credentials are not an API key
- {}
- has no supported external credential owner
- Kimi CLI credentials are never imported; configure a Kimi…
- MCP server rejected the request with and refreshing the…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/6285e5cd8d30db3b.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/config.rs:12502
route_config: &Config,
api_key: &str,
) -> Result<SavedCredential> {
if identity.provider == ApiProvider::Xai {
return codewhale_config::with_xai_oauth_revocation_transaction(|| {
save_api_key_for_identity_unlocked(identity, route_config, api_key)
});
}
save_api_key_for_identity_unlocked(identity, route_config, api_key)
}
fn save_api_key_for_identity_unlocked(
identity: &ProviderIdentity,
route_config: &Config,
api_key: &str,
) -> Result<SavedCredential> {
let provider = identity.provider;
if provider == ApiProvider::OpenaiCodex {
anyhow::bail!(
"OpenAI Codex uses OAuth. Sign in with ChatGPT via `codewhale auth chatgpt` (subscription billing, Codewhale-owned tokens). The openai API-key route is a different billing owner. Alternatively run `codex login`, then grant exact read-only access with `codewhale auth external-consent --provider openai-codex --mode read-only`, or set OPENAI_CODEX_ACCESS_TOKEN for this process; Codewhale does not store an API key for this provider."
);
}
let is_legacy_literal_custom = provider == ApiProvider::Custom
&& identity.key.trim() == ApiProvider::Custom.as_str()
&& identity.persisted_id().is_none();
if matches!(provider, ApiProvider::Deepseek | ApiProvider::DeepseekCN) {
return save_api_key(api_key);
}
if is_legacy_literal_custom {
return save_root_api_key_for_secret_slot(api_key, "custom", false);
}
let api_key = api_key.trim();
anyhow::ensure!(!api_key.is_empty(), "Refusing to save an empty API key.");
let config_path =
credential_config_path().context("Failed to resolve config path for provider API key.")?;View on GitHub (pinned to 73e0f67d83)