zeroclaw-labs/zeroclaw · error · anyhow::Error
Gemini CLI OAuth token expired and no refresh_token availabl
Error message
Gemini CLI OAuth token expired and no refresh_token available — re-run `gemini` to authenticate
What it means
The Gemini CLI OAuth credential state holds an access token that is expired (or within the 60-second pre-expiry refresh buffer, or of unknown expiry) and has no refresh_token, so get_valid_oauth_token cannot renew it in-process. The only recovery is re-authenticating through the Gemini CLI, as the message states.
Source
Thrown at crates/zeroclaw-providers/src/gemini.rs:834
.is_none_or(|exp| exp <= now_millis.saturating_add(60_000));
if needs_refresh {
if let Some(ref refresh_token) = guard.refresh_token {
let refreshed = refresh_gemini_cli_token_async(
refresh_token,
guard.client_id.as_deref(),
guard.client_secret.as_deref(),
)
.await?;
::zeroclaw_log::record!(
INFO,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note),
"Gemini CLI OAuth token refreshed successfully (runtime)"
);
guard.access_token = refreshed.access_token;
guard.expiry_millis = refreshed.expiry_millis;
} else {
anyhow::bail!(
"Gemini CLI OAuth token expired and no refresh_token available — re-run `gemini` to authenticate"
);
}
}
Ok(guard.access_token.clone())
}
/// Rotate to the next available OAuth credentials file and swap state.
/// Returns `true` when rotation succeeded.
async fn rotate_oauth_credential(
&self,
state: &Arc<tokio::sync::Mutex<OAuthTokenState>>,
) -> bool {
if self.oauth_cred_paths.len() <= 1 {
return false;
}
View on GitHub (pinned to 88bb9c8533)
Solutions
- Run `gemini` and log in again so a complete oauth_creds.json (with refresh_token) is written
- Delete the stale credential files under the Gemini CLI config dir and log in fresh
- Or configure a Gemini API key on the alias to bypass CLI OAuth entirely
- If this recurs after every restart, check that nothing truncates or rewrites the credential file
Defensive patterns
Strategy: fallback
Validate before calling
// Check credential completeness before building the provider
if gemini_cli_creds_present() && !gemini_cli_creds_have_refresh_token() {
// incomplete credentials: fail fast with a re-login hint at startup
anyhow::bail!("Gemini CLI creds lack refresh_token; re-run `gemini`");
} Try / catch
match provider.chat(/* ... */).await {
Ok(resp) => Ok(resp),
Err(e) if e.to_string().contains("no refresh_token available") => {
// terminal for this credential set: instruct `gemini` re-login,
// or fall back to an API-key-based gemini alias
fallback_to_api_key_alias().await
}
Err(e) => Err(e),
} Prevention
- After Gemini CLI upgrades, re-login once so complete credentials are written
- Never hand-edit or truncate oauth_creds.json
- Use GeminiModelProvider::has_cli_credentials() plus a refresh_token check at startup
- Configure an API-key alias as fallback for unattended deployments
When it happens
Trigger: get_valid_oauth_token needs a refresh but guard.refresh_token is None: the loaded ~/.gemini credential file contains an access token without a refresh_token field - an old format, a partially-written file, or a token saved before full consent.
Common situations: Upgraded or uninstalled Gemini CLI leaving stale credentials; credential files copied or edited without the refresh_token field; headless setups that never completed the full CLI login.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Google device code request failed ({}): {}
- Gemini CLI OAuth refresh failed (HTTP {status}): {body}
- Device code expired before authorization was completed
- User denied authorization
- Device code expired
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/3c2f16262e93229d.
Report an issue: GitHub.