zeroclaw-labs/zeroclaw · error
Gemini auth profile is missing token set: {profile_id}
Error message
Gemini auth profile is missing token set: {profile_id} What it means
Post-lock twin of error 673 in get_valid_gemini_access_token: after taking the per-profile refresh mutex and re-loading the store, the profile's token_set is now None. The profile changed between the two loads — a concurrent write replaced the OAuth token set with a bearer token or dropped it.
Source
Thrown at crates/zeroclaw-providers/src/auth/mod.rs:324
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;
// Re-load after waiting for lock to avoid duplicate refreshes.
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!("Gemini 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!(
"Gemini token refresh is in backoff for {remaining}s due to previous failures"
);
}
let mut refreshed = match refresh_gemini_access_token_with_retries(
&self.client,
client_id,
client_secret,View on GitHub (pinned to 88bb9c8533)
Solutions
- Retry the credential resolution once the concurrent profile write completes
- Keep one writer for the auth profiles file (single instance or single state dir)
- Do not flip a gemini profile between OAuth and token kinds while requests are in flight
Defensive patterns
Strategy: retry
Validate before calling
// Single-writer profiles file avoids token_set disappearing between loads.
let data = auth.load_profiles().await?;
anyhow::ensure!(
data.profiles.get(&profile_id).map(|p| p.token_set.is_some()).unwrap_or(false),
"gemini 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_gemini_access_token(override_, cid, secret).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_gemini_access_token(override_, cid, secret).await?
}
Err(e) => return Err(e),
} Prevention
- One state dir per process to avoid concurrent profile rewrites
- Retry once after a brief pause when this lands during parallel auth commands
- Never switch a gemini profile between token kinds while requests run
When it happens
Trigger: A concurrent auth paste-token / profile overwrite lands between the first store load and the post-lock re-load inside get_valid_gemini_access_token, while send_generate_content or warmup was resolving credentials.
Common situations: CLI auth commands and a running gateway share one profiles file; two instances point at the same state dir; external tooling rewrites the profiles JSON.
Related errors
- OpenAI Codex auth profile is missing token set: {profile_id}
- Gemini auth profile is not OAuth-based: {profile_id}
- xAI auth profile is missing token set: {profile_id}
- OpenAI Codex auth profile is not OAuth-based: {profile_id}
- Gemini token refresh is in backoff for {remaining}s due to p
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/93d7b9951b1e6a0b.
Report an issue: GitHub.