Hmbown/CodeWhale · error
xAI OAuth access token in
Error message
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`. What it means
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.
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.
Example fix
// before $ codewhale ... # reads expired grok token Error: xAI OAuth access token in /home/me/.grok/auth.json is expired... // after $ grok login # refresh the external credential # or $ codewhale auth xai-device
Defensive patterns
Strategy: fallback
Validate before calling
// caller-side freshness sanity: compare the file's mtime / expiry if exposed,
// otherwise detect the error and route to re-login
fn is_stale_import(err: &anyhow::Error) -> bool {
err.to_string().contains("access token in") && err.to_string().contains("is expired")
} Try / catch
match ensure_xai_import_usable(grant) {
Err(e) if e.to_string().contains("is expired") => {
// prompt: `grok login` or `codewhale auth xai-device`
}
other => other,
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Codewhale-owned xAI OAuth credentials are inactive until…
- invalid Codewhale-owned xAI OAuth generation; expected…
- stored MCP OAuth credential for server
- xAI OAuth credentials at
- xAI OAuth credentials not found. Options: 1. Run `codewhale…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/35efac6b9a02b6c8.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/oauth.rs:2102
/// Grant-time validation for an external Grok CLI credential file (#5772).
///
/// Reads exactly the granted path through the secure adapter and requires a
/// usable, unexpired entry. Never refreshes, rewrites, or makes a network
/// request. Consent is persisted only after this succeeds, so a consent
/// record can never be written for a file that holds nothing usable.
pub fn validate_grok_external_credentials(
grant: &codewhale_config::ExternalCredentialReadGrant,
) -> Result<()> {
let mut file = load_external_auth_file(grant)?;
let (_, entry) = select_entry(OAuthProvider::Xai, &mut file).ok_or_else(|| {
anyhow::anyhow!(
"xAI OAuth credentials at {} have no usable entry. Run `grok login` again or use `codewhale auth xai-device` for Codewhale-owned storage.",
codewhale_config::quote_os_path(grant.path())
)
})?;
if !entry_access_token_is_fresh(&entry) {
bail!(
"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`.",
codewhale_config::quote_os_path(grant.path())
);
}
Ok(())
}
/// Load xAI OAuth credentials with full precedence: configured generation,
/// legacy owned file, then the consented Grok CLI import. Codewhale-owned
/// credentials may refresh and rewrite Codewhale-owned storage; external
/// credentials are read-only.
pub fn get_xai_credentials(config: &Config) -> Result<OwnedOAuthCredentials> {
anyhow::ensure!(
config.api_provider() == crate::config::ApiProvider::Xai
&& config
.provider_config_for(crate::config::ApiProvider::Xai)
.and_then(|entry| entry.auth_mode.as_deref())
.is_some_and(auth_mode_uses_xai_oauth),View on GitHub (pinned to 73e0f67d83)