zeroclaw-labs/zeroclaw · error

xAI auth profile is missing token set

Error message

xAI auth profile is missing token set: {profile_id}

What it means

Post-lock twin of error 676 in get_valid_xai_access_token: after acquiring the per-profile refresh lock, the re-loaded profile no longer carries a token_set. A concurrent write changed the profile from OAuth to token kind (or removed the set) between the two store loads inside this resolver.

Solutions

  1. Retry once the profile write settles
  2. Use one state dir per process so the profiles file has a single writer
  3. Keep the xai profile on one credential kind instead of switching it while traffic runs
Defensive patterns

Strategy: retry

Validate before calling

let data = auth.load_profiles().await?;
anyhow::ensure!(
    data.profiles.get(&profile_id).map(|p| p.token_set.is_some()).unwrap_or(false),
    "xai profile lost its token set mid-flight"
);

Type guard

fn is_oauth_profile(p: &AuthProfile) -> bool {
    p.token_set.is_some()
}

Try / catch

match auth.get_valid_xai_access_token(override_).await {
    Ok(tok) => tok,
    Err(e) if e.to_string().contains("missing token set") => {
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        auth.get_valid_xai_access_token(override_).await?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: resolve_credential runs while the same xai profile is overwritten by a concurrent paste-token or external edit; the first load sees a token_set, the second does not, and the call bails.

Common situations: Shared state dir between CLI and long-running process; parallel scripts rewriting the profiles file.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/2d83bf7493cf23c0. Report an issue: GitHub.

Appendix: source

Thrown at crates/zeroclaw-providers/src/auth/mod.rs:419

        };

        if !token_set.is_expiring_within(Duration::from_secs(OPENAI_REFRESH_SKEW_SECS)) {
            return Ok(Some(token_set.access_token.clone()));
        }

        let Some(refresh_token) = token_set.refresh_token.clone() else {
            return Ok(Some(token_set.access_token.clone()));
        };

        let refresh_lock = refresh_lock_for_profile(&profile_id);
        let _guard = refresh_lock.lock().await;

        let data = self.store.load().await?;
        let Some(latest_profile) = data.profiles.get(&profile_id) else {
            return Ok(None);
        };
        let Some(latest_tokens) = latest_profile.token_set.as_ref() else {
            anyhow::bail!("xAI auth profile is missing token set: {profile_id}");
        };
        if !latest_tokens.is_expiring_within(Duration::from_secs(OPENAI_REFRESH_SKEW_SECS)) {
            return Ok(Some(latest_tokens.access_token.clone()));
        }

        let refresh_token = latest_tokens.refresh_token.clone().unwrap_or(refresh_token);
        if let Some(remaining) = refresh_backoff_remaining(&profile_id) {
            anyhow::bail!(
                "xAI token refresh is in backoff for {remaining}s due to previous failures"
            );
        }

        let mut refreshed =
            match refresh_xai_access_token_with_retries(&self.client, &refresh_token).await {
                Ok(tokens) => {
                    clear_refresh_backoff(&profile_id);
                    tokens
                }

View on GitHub (pinned to 88bb9c8533)