{"record":{"id":"35efac6b9a02b6c8","repo":"Hmbown/CodeWhale","slug":"xai-oauth-access-token-in-is-expired-read-only-consent-never","errorCode":null,"errorMessage":"xAI OAuth access token in {} is expired. Read-only consent never refreshes or rewrites another CLI's credentials. Run `grok login` again or use `codewhale auth xai-device`.","messagePattern":"xAI OAuth access token in (.+?) is expired\\. Read-only consent never refreshes or rewrites another CLI's credentials\\. Run `grok login` again or use `codewhale auth xai-device`\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/oauth.rs","lineNumber":2102,"sourceCode":"\n/// Grant-time validation for an external Grok CLI credential file (#5772).\n///\n/// Reads exactly the granted path through the secure adapter and requires a\n/// usable, unexpired entry. Never refreshes, rewrites, or makes a network\n/// request. Consent is persisted only after this succeeds, so a consent\n/// record can never be written for a file that holds nothing usable.\npub fn validate_grok_external_credentials(\n    grant: &codewhale_config::ExternalCredentialReadGrant,\n) -> Result<()> {\n    let mut file = load_external_auth_file(grant)?;\n    let (_, entry) = select_entry(OAuthProvider::Xai, &mut file).ok_or_else(|| {\n        anyhow::anyhow!(\n            \"xAI OAuth credentials at {} have no usable entry. Run `grok login` again or use `codewhale auth xai-device` for Codewhale-owned storage.\",\n            codewhale_config::quote_os_path(grant.path())\n        )\n    })?;\n    if !entry_access_token_is_fresh(&entry) {\n        bail!(\n            \"xAI OAuth access token in {} is expired. Read-only consent never refreshes or rewrites another CLI's credentials. Run `grok login` again or use `codewhale auth xai-device`.\",\n            codewhale_config::quote_os_path(grant.path())\n        );\n    }\n    Ok(())\n}\n\n/// Load xAI OAuth credentials with full precedence: configured generation,\n/// legacy owned file, then the consented Grok CLI import. Codewhale-owned\n/// credentials may refresh and rewrite Codewhale-owned storage; external\n/// credentials are read-only.\npub fn get_xai_credentials(config: &Config) -> Result<OwnedOAuthCredentials> {\n    anyhow::ensure!(\n        config.api_provider() == crate::config::ApiProvider::Xai\n            && config\n                .provider_config_for(crate::config::ApiProvider::Xai)\n                .and_then(|entry| entry.auth_mode.as_deref())\n                .is_some_and(auth_mode_uses_xai_oauth),","sourceCodeStart":2084,"sourceCodeEnd":2120,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/oauth.rs#L2084-L2120","documentation":"While validating an imported xAI credential entry, the access token fails the freshness check (`entry_access_token_is_fresh`). Because the import path is read-only consent — Codewhale never refreshes or rewrites another CLI's credential file — it refuses to proceed and tells the user to re-authenticate. This is a deliberate data-integrity guard, not a bug.","triggerScenarios":"The validity check (`ensure_external_xai_credentials_usable`-style function) reads the granted auth file, finds a usable entry, but the entry's access token is expired (`entry_access_token_is_fresh(&entry)` returns false) and there is no acceptable refresh path.","commonSituations":"User logged into `grok` long ago; the token expired while offline; the other CLI can't refresh because it too is unauthenticated; running Codewhale against a stale imported grant.","solutions":["Run `grok login` again to mint a fresh access token in the external CLI's file.","Or use Codewhale-owned storage: `codewhale auth xai-device`, which can refresh its own tokens.","If you believe the token is fresh, check clock skew / system time on the machine."],"exampleFix":"// before\n$ codewhale ...   # reads expired grok token\nError: xAI OAuth access token in /home/me/.grok/auth.json is expired...\n// after\n$ grok login       # refresh the external credential\n# or\n$ codewhale auth xai-device","handlingStrategy":"fallback","validationCode":"// caller-side freshness sanity: compare the file's mtime / expiry if exposed,\n// otherwise detect the error and route to re-login\nfn is_stale_import(err: &anyhow::Error) -> bool {\n    err.to_string().contains(\"access token in\") && err.to_string().contains(\"is expired\")\n}","typeGuard":null,"tryCatchPattern":"match ensure_xai_import_usable(grant) {\n    Err(e) if e.to_string().contains(\"is expired\") => {\n        // prompt: `grok login` or `codewhale auth xai-device`\n    }\n    other => other,\n}","preventionTips":["Refresh the external CLI's login regularly; imported tokens never auto-refresh.","Prefer Codewhale-owned credentials for anything long-lived.","Keep system clocks synced (NTP) to avoid false expiry."],"tags":["oauth","token-expired","xai","read-only"],"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-23T08:17:48.524Z"}