{"record":{"id":"1648becbb967d5fd","repo":"Hmbown/CodeWhale","slug":"codex-access-token-in-is-expired-read-only-consent-never","errorCode":null,"errorMessage":"Codex access token in {} is expired. Read-only consent never refreshes or rewrites another CLI's credentials. Sign in with ChatGPT via `codewhale auth chatgpt`, run `codex login` again, or provide OPENAI_CODEX_ACCESS_TOKEN for this process.","messagePattern":"Codex access token in (.+?) is expired\\. Read-only consent never refreshes or rewrites another CLI's credentials\\. Sign in with ChatGPT via `codewhale auth chatgpt`, run `codex login` again, or provide OPENAI_CODEX_ACCESS_TOKEN for this process\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/oauth.rs","lineNumber":182,"sourceCode":"pub fn stored_credentials_present(grant: &ExternalCredentialReadGrant) -> bool {\n    load_credentials(grant)\n        .ok()\n        .flatten()\n        .is_some_and(|credentials| !token_is_expired(&credentials.access_token))\n}\n\n/// Load read-only credentials from the exact external path authorized by\n/// `grant`. Expired tokens fail with guidance; they are never refreshed.\npub fn get_credentials(grant: &ExternalCredentialReadGrant) -> Result<CodexCredentials> {\n    let creds =\n        load_credentials(grant)?.with_context(|| missing_auth_message(OAuthProvider::Chatgpt))?;\n\n    // Check if the access token is still valid.\n    if !token_is_expired(&creds.access_token) {\n        return Ok(creds);\n    }\n\n    bail!(\n        \"Codex access token in {} is expired. Read-only consent never refreshes or rewrites another CLI's credentials. Sign in with ChatGPT via `codewhale auth chatgpt`, run `codex login` again, or provide OPENAI_CODEX_ACCESS_TOKEN for this process.\",\n        codewhale_config::quote_os_path(grant.path())\n    )\n}\n\n/// Read a ChatGPT account id from env overrides only.\nfn codex_account_id_env() -> Option<String> {\n    for var in [\"OPENAI_CODEX_ACCOUNT_ID\", \"CODEX_ACCOUNT_ID\"] {\n        if let Ok(value) = std::env::var(var) {\n            let trimmed = value.trim();\n            if !trimmed.is_empty() {\n                return Some(trimmed.to_string());\n            }\n        }\n    }\n    None\n}\n","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/oauth.rs#L164-L200","documentation":"The Codex OAuth access token stored by the external `codex` CLI is expired. Codewhale only holds read-only consent to that credential file and deliberately never refreshes or rewrites another CLI's tokens, so instead of silently refreshing it fails and tells you how to obtain a fresh token. The error names the credential file path so you know exactly which grant is stale.","triggerScenarios":"Calling get_credentials when token_is_expired(&creds.access_token) is true for the Codex grant, i.e. no valid token exists and none will be refreshed on your behalf.","commonSituations":"Using Codex sign-in after it has been idle past the access-token lifetime; a long-running session outliving the token; switching machines where the cached Codex token is stale; OPENAI_CODEX_ACCESS_TOKEN unset in the current process.","solutions":["Sign in with ChatGPT via `codewhale auth chatgpt` to use Codewhale's own credentials.","Run `codex login` again to refresh the Codex CLI's stored token.","Set OPENAI_CODEX_ACCESS_TOKEN in the environment for this process.","Check the token file's modified time and expiry to confirm which credential is stale."],"exampleFix":"// before\nlet creds = get_credentials()?; // Codex access token ... is expired\n// after\n$ export OPENAI_CODEX_ACCESS_TOKEN=<fresh-token>\nlet creds = get_credentials()?;","handlingStrategy":"try-catch","validationCode":"// preflight: decode the JWT exp claim yourself before relying on the grant\nfn token_expired(jwt: &str) -> bool {\n    let claims: serde_json::Value = decode_payload_unverified(jwt);\n    claims[\"exp\"].as_i64().map(|e| e < now_unix()).unwrap_or(true)\n}","typeGuard":null,"tryCatchPattern":"match get_credentials() {\n    Ok(creds) => creds,\n    Err(e) if e.to_string().contains(\"is expired\") => {\n        // prompt: run `codex login` or `codewhale auth chatgpt`, or set OPENAI_CODEX_ACCESS_TOKEN\n        refresh_out_of_band()?;\n        get_credentials().context(\"token still expired after re-auth\")?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Check token expiry before starting long sessions and re-auth proactively.","Prefer `codewhale auth chatgpt` for flows where Codewhale owns the credential lifecycle.","Set OPENAI_CODEX_ACCESS_TOKEN in automation environments where interactive login is impossible.","Remember Codewhale never rewrites another CLI's credential file; schedule external `codex login` refreshes in cron/CI."],"tags":["oauth","token","expired","codex","credentials"],"backgroundTag":"jwt-token-expired","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}