Hmbown/CodeWhale · error · anyhow::Error
OpenAI Codex OAuth credentials are unavailable. Codewhale…
Error message
OpenAI Codex OAuth credentials are unavailable.
Codewhale checks OPENAI_CODEX_ACCESS_TOKEN and CODEX_ACCESS_TOKEN automatically.
Access to the Codex CLI file is disabled by default. After `codex login`, grant read-only access explicitly with:
`codewhale auth external-consent --provider openai-codex --mode read-only --path {}`
Read-only access never refreshes or rewrites the Codex CLI file. What it means
The OpenAI Codex route resolves OAuth credentials via crate::oauth, which checks OPENAI_CODEX_ACCESS_TOKEN and CODEX_ACCESS_TOKEN automatically, and reads the Codex CLI auth file only under an explicit read-only consent grant (disabled by default). When none of these yield credentials, get_credentials attaches missing_auth_message (crates/tui/src/oauth.rs:174) as context and the provider arm bails with it. The message stresses that read-only consent never refreshes or rewrites the Codex CLI file.
Solutions
- Export OPENAI_CODEX_ACCESS_TOKEN (or CODEX_ACCESS_TOKEN) for this process
- Run 'codex login' once, then grant read-only access: codewhale auth external-consent --provider openai-codex --mode read-only --path <auth-file>
- Verify the consent path matches the Codex CLI auth file location printed in the error
Example fix
# before $ codewhale --provider openai-codex # error: OpenAI Codex OAuth credentials are unavailable... # after $ codex login $ codewhale auth external-consent --provider openai-codex --mode read-only --path ~/.codex/auth.json $ codewhale --provider openai-codex
Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight for the openai-codex OAuth route:
fn codex_env_token() -> Option<String> {
["OPENAI_CODEX_ACCESS_TOKEN", "CODEX_ACCESS_TOKEN"]
.iter()
.map(|v| std::env::var(v).ok())
.flatten()
.map(|s| s.trim().to_string())
.find(|s| !s.is_empty())
}
if provider == ApiProvider::OpenaiCodex {
assert!(codex_env_token().is_some() || read_only_grant_exists("openai-codex"),
"set OPENAI_CODEX_ACCESS_TOKEN or grant read-only consent after codex login");
} Type guard
fn has_codex_oauth_source(env: Option<&str>, has_grant: bool) -> bool {
env.is_some_and(|t| !t.trim().is_empty()) || has_grant
} Try / catch
match crate::oauth::get_credentials(&grant) {
Err(e) if e.to_string().contains("OpenAI Codex OAuth credentials are unavailable") => {
// print the external-consent command with the exact --path from the message
}
other => other?,
} Prevention
- Codewhale never reads the Codex CLI file without consent - plan the explicit external-consent step after 'codex login'
- Use OPENAI_CODEX_ACCESS_TOKEN in CI to avoid touching the CLI file at all
- Read-only consent never refreshes tokens; an expired Codex token surfaces as a separate expiry error requiring a fresh 'codex login'
When it happens
Trigger: provider == ApiProvider::OpenaiCodex and the OpenaiCodex arm calls crate::oauth::missing_auth_message(); both env vars are unset/empty, and there is no read-only external-consent grant for the Codex CLI auth file (or the file does not exist because 'codex login' was never run).
Common situations: Expecting Codewhale to auto-import a Codex CLI login without granting consent; env vars set in a different shell; token expired is a separate later error; hardened setups where the CLI file exists but access is deliberately disabled.
Related errors
- agy OAuth token JSON carries no access token member
- agy OAuth token member
- atomically replacing xAI OAuth credentials
- bearer credentials are not an API key
- Codewhale-owned OAuth credentials at
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/dd243e011dcb3c60.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/config.rs:6715
anyhow::bail!(
"Moonshot/Kimi API key not found. Get a key: {}. Run 'codewhale auth set --provider moonshot', \
set {}, or add [{}] api_key. \
For a Kimi Code plan key, set [providers.moonshot] base_url = \
\"https://api.kimi.com/coding/v1\" and model = \"kimi-for-coding\".",
credential_help
.credential_url
.unwrap_or("https://platform.kimi.ai/console/api-keys"),
provider.env_vars_label(),
provider_config_table_name(provider)?
);
}
ApiProvider::Anthropic | ApiProvider::Openmodel => {
anyhow::bail!("{}", missing_provider_api_key_message(provider)?)
}
ApiProvider::OpencodeZen => {
anyhow::bail!("{}", missing_provider_api_key_message(provider)?)
}
ApiProvider::OpenaiCodex => anyhow::bail!("{}", crate::oauth::missing_auth_message()),
ApiProvider::Xai => {
// Prefer OAuth guidance when auth_mode requests it or Grok CLI
// tokens already exist; otherwise show both API-key and OAuth.
if self
.provider_config_for(provider)
.is_some_and(provider_config_uses_xai_oauth)
|| crate::xai_oauth::credentials_present(self)
{
anyhow::bail!("{}", crate::xai_oauth::missing_auth_message());
}
anyhow::bail!(
"xAI API key not found. Get a key: https://console.x.ai/\n\
Run 'codewhale auth set --provider xai', set XAI_API_KEY, or add \
[providers.xai] api_key.\n\
OAuth alternative: run `codewhale auth xai-device` for \
Codewhale-owned storage and set [providers.xai] auth_mode = \"oauth\"."
);
}View on GitHub (pinned to 0c42157ee5)