zeroclaw-labs/zeroclaw · error · anyhow::Error

Gemini API error: {}

Error message

Gemini API error: {}

What it means

Gemini answered HTTP 200 but the JSON body carries a top-level error object (GenerateContentResponse.error). ZeroClaw treats this in-band error as fatal and surfaces err.message; some auth restrictions and abuse-policy blocks are reported this way despite the success status.

Source

Thrown at crates/zeroclaw-providers/src/gemini.rs:1366

                        project.as_deref(),
                        oauth_token.as_deref(),
                    )?
                    .send()
                    .await?;
            } else {
                anyhow::bail!("Gemini API error ({status}): {error_text}");
            }
        }

        if !response.status().is_success() {
            let status = response.status();
            let error_text = response.text().await.unwrap_or_default();
            anyhow::bail!("Gemini API error ({status}): {error_text}");
        }

        let result: GenerateContentResponse = response.json().await?;
        if let Some(err) = &result.error {
            anyhow::bail!("Gemini API error: {}", err.message);
        }
        let result = result.into_effective_response();
        if let Some(err) = result.error {
            anyhow::bail!("Gemini API error: {}", err.message);
        }

        let usage = result
            .usage_metadata
            .and_then(Self::token_usage_from_metadata);

        let text = result
            .candidates
            .and_then(|c| c.into_iter().next())
            .and_then(|c| c.content)
            .and_then(|c| c.effective_text())
            .ok_or_else(|| {
                ::zeroclaw_log::record!(
                    ERROR,

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Match on the message: API_KEY_INVALID -> recreate the key without referrer/IP restrictions; safety block -> rephrase input or adjust safety settings where policy allows
  2. Log err.message verbatim for support escalation
  3. Retry only when the message clearly indicates a transient condition

Example fix

// after - surface the in-band message for routing decisions
if let Err(e) = provider.chat_with_system(None, prompt, model, None).await {
    let msg = e.to_string();
    if msg.contains("API_KEY_INVALID") {
        return Err(anyhow::anyhow!("recreate GEMINI_API_KEY without referrer restrictions"));
    }
    return Err(e);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: catch restricted keys before chat calls
async fn gemini_key_unrestricted() -> anyhow::Result<()> {
    // a 1-token test request surfaces API_KEY_INVALID early
    Ok(())
}

Type guard

fn is_gemini_in_band_error(e: &anyhow::Error) -> bool {
    e.to_string().starts_with("Gemini API error: ")
}

Try / catch

if let Err(e) = provider.chat_with_system(None, prompt, model, None).await {
    let msg = e.to_string();
    if msg.contains("API_KEY_INVALID") {
        return Err(anyhow::anyhow!("recreate the key without referrer/IP restrictions"));
    }
    if msg.contains("safety") || msg.contains("block") {
        return Err(anyhow::anyhow!("input tripped a content filter: {msg}"));
    }
    return Err(e);
}

Prevention

When it happens

Trigger: HTTP-referrer or IP-restricted API keys used from a server (key validates at routing, fails at execution); content that trips safety filters; endpoints that fold stream failures into a single error field.

Common situations: API key created with browser referrer restrictions then used server-side; safety-filter blocks on borderline prompts; partially rolled-out model endpoints.

Related errors


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