{"record":{"id":"dd243e011dcb3c60","repo":"Hmbown/CodeWhale","slug":"openai-codex-oauth-credentials-are-unavailable-c","errorCode":null,"errorMessage":"OpenAI Codex OAuth credentials are unavailable.\n\nCodewhale checks OPENAI_CODEX_ACCESS_TOKEN and CODEX_ACCESS_TOKEN automatically.\nAccess to the Codex CLI file is disabled by default. After `codex login`, grant read-only access explicitly with:\n`codewhale auth external-consent --provider openai-codex --mode read-only --path {}`\nRead-only access never refreshes or rewrites the Codex CLI file.","messagePattern":"OpenAI Codex OAuth credentials are unavailable\\.\n\nCodewhale checks OPENAI_CODEX_ACCESS_TOKEN and CODEX_ACCESS_TOKEN automatically\\.\nAccess to the Codex CLI file is disabled by default\\. After `codex login`, grant read-only access explicitly with:\n`codewhale auth external-consent --provider openai-codex --mode read-only --path (.+?)`\nRead-only access never refreshes or rewrites the Codex CLI file\\.","errorType":"console","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/config.rs","lineNumber":6409,"sourceCode":"                anyhow::bail!(\n                    \"Moonshot/Kimi API key not found. Get a key: {}. Run 'codewhale auth set --provider moonshot', \\\n                     set {}, or add [{}] api_key. \\\n                     For a Kimi Code plan key, set [providers.moonshot] base_url = \\\n                     \\\"https://api.kimi.com/coding/v1\\\" and model = \\\"kimi-for-coding\\\".\",\n                    credential_help\n                        .credential_url\n                        .unwrap_or(\"https://platform.kimi.ai/console/api-keys\"),\n                    provider.env_vars_label(),\n                    provider_config_table_name(provider)?\n                );\n            }\n            ApiProvider::Anthropic | ApiProvider::Openmodel => {\n                anyhow::bail!(\"{}\", missing_provider_api_key_message(provider)?)\n            }\n            ApiProvider::OpencodeZen => {\n                anyhow::bail!(\"{}\", missing_provider_api_key_message(provider)?)\n            }\n            ApiProvider::OpenaiCodex => anyhow::bail!(\"{}\", crate::oauth::missing_auth_message()),\n            ApiProvider::Xai => {\n                // Prefer OAuth guidance when auth_mode requests it or Grok CLI\n                // tokens already exist; otherwise show both API-key and OAuth.\n                if self\n                    .provider_config_for(provider)\n                    .is_some_and(provider_config_uses_xai_oauth)\n                    || crate::xai_oauth::credentials_present(self)\n                {\n                    anyhow::bail!(\"{}\", crate::xai_oauth::missing_auth_message());\n                }\n                anyhow::bail!(\n                    \"xAI API key not found. Get a key: https://console.x.ai/\\n\\\n                     Run 'codewhale auth set --provider xai', set XAI_API_KEY, or add \\\n                     [providers.xai] api_key.\\n\\\n                     OAuth alternative: run `codewhale auth xai-device` for \\\n                     Codewhale-owned storage and set [providers.xai] auth_mode = \\\"oauth\\\".\"\n                );\n            }","sourceCodeStart":6391,"sourceCodeEnd":6427,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/config.rs#L6391-L6427","documentation":"ApiProvider::OpenaiCodex delegates to oauth::missing_auth_message() (crates/tui/src/oauth.rs:174). Codewhale reads Codex OAuth only from OPENAI_CODEX_ACCESS_TOKEN / CODEX_ACCESS_TOKEN or, with explicit read-only consent, the Codex CLI's auth.json (path interpolated into the message). Access to that file is disabled by default and Codewhale never refreshes or rewrites it, so an expired token must be refreshed by `codex login`.","triggerScenarios":"provider = openai-codex with neither env token set, no Codewhale-owned OAuth storage, and no read-only consent for the Codex CLI auth.json; or consent granted but the underlying token expired (a sibling error points back to `codex login`).","commonSituations":"Users assuming `codex login` alone is enough (consent is a separate step); CI without the env tokens; rotating machines where auth.json exists but was never consented.","solutions":["Provide a token via OPENAI_CODEX_ACCESS_TOKEN or CODEX_ACCESS_TOKEN for this process.","Or after `codex login`, grant read-only access: codewhale auth external-consent --provider openai-codex --mode read-only --path <auth.json path from the message>.","If the token is expired, run `codex login` again — Codewhale never refreshes the external file."],"exampleFix":"# before: codex login done, but no consent, no env token\nprovider = \"openai-codex\"\n\n# after (terminal)\n# codewhale auth external-consent --provider openai-codex --mode read-only --path ~/.codex/auth.json\n# or: export OPENAI_CODEX_ACCESS_TOKEN=...","handlingStrategy":"fallback","validationCode":"fn codex_token_available() -> bool {\n    [\"OPENAI_CODEX_ACCESS_TOKEN\", \"CODEX_ACCESS_TOKEN\"]\n        .iter()\n        .any(|v| std::env::var(v).map(|s| !s.trim().is_empty()).unwrap_or(false))\n}\n\nif !codex_token_available() {\n    // plan B: prompt for external-consent on the Codex CLI auth.json,\n    // or deselect openai-codex before dispatch\n}","typeGuard":"fn codex_oauth_ready(config: &Config) -> bool {\n    codex_token_available()\n        || crate::oauth::stored_credentials_present_if_consented(config) // consent + unexpired token\n}","tryCatchPattern":"match config.deepseek_api_key() {\n    Err(e) if e.to_string().starts_with(\"OpenAI Codex OAuth credentials are unavailable\") => {\n        // fall back: another provider route, or surface the external-consent command\n        Err(e)\n    }\n    other => other,\n}","preventionTips":["`codex login` alone is never enough — grant read-only consent explicitly afterwards.","In CI, prefer OPENAI_CODEX_ACCESS_TOKEN over file consent.","Expired Codex tokens must be refreshed by codex login; Codewhale never rewrites that file."],"tags":["rust","openai-codex","oauth","external-consent","credentials","security"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}