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

  1. Run `grok login` again to mint a fresh access token in the external CLI's file.
  2. Or use Codewhale-owned storage: `codewhale auth xai-device`, which can refresh its own tokens.
  3. 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

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


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)