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

  1. Run `codewhale auth chatgpt` to sign in with ChatGPT OAuth (subscription billing)
  2. Or run `codex login`, then `codewhale auth external-consent --provider openai-codex --mode read-only`
  3. 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

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


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)