zeroclaw-labs/zeroclaw · error

Gemini auth profile is not OAuth-based: {profile_id}

Error message

Gemini auth profile is not OAuth-based: {profile_id}

What it means

The Gemini counterpart of error 670: get_valid_gemini_access_token selected a gemini profile whose token_set is None, meaning the stored credential is a bearer token rather than an OAuth token set. The function needs a token_set to check expiry and refresh; a token-kind profile violates that contract and bails.

Source

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

    }

    pub async fn get_valid_gemini_access_token(
        &self,
        profile_override: Option<&str>,
        client_id: &str,
        client_secret: &str,
    ) -> Result<Option<String>> {
        let data = self.store.load().await?;
        let Some(profile_id) = select_profile_id(&data, GEMINI_PROVIDER, profile_override) else {
            return Ok(None);
        };

        let Some(profile) = data.profiles.get(&profile_id) else {
            return Ok(None);
        };

        let Some(token_set) = profile.token_set.as_ref() else {
            anyhow::bail!("Gemini auth profile is not OAuth-based: {profile_id}");
        };

        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);
        };

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Run auth login --model-provider gemini --profile <name> to complete the Google OAuth flow and store a token_set
  2. Verify you are selecting the right profile: list profiles and their kinds before passing profile_override
  3. If a bearer/API-key profile is intended, route through get_provider_bearer_token instead
Defensive patterns

Strategy: validation

Validate before calling

let data = auth.load_profiles().await?;
if let Some(profile) = data.profiles.get(&format!("gemini:{}", name)) {
    anyhow::ensure!(profile.token_set.is_some(), "gemini profile {name} is a bearer token, not OAuth");
}
let token = auth.get_valid_gemini_access_token(Some(name), client_id, client_secret).await?;

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("not OAuth-based") => {
        eprintln!("run auth login --model-provider gemini to create an OAuth profile");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling send_generate_content, warmup, or refresh_status for gemini when the active profile was created via auth paste-token; selecting such a profile through profile_override; a hand-edited or migrated profiles file with a null token_set.

Common situations: User pasted an API key for Gemini but the runtime path calls the OAuth resolver; the alias profile in [providers.models.gemini.<profile>] was set up with a token instead of the OAuth login flow.

Related errors


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