{"record":{"id":"ad371d919b848608","repo":"zeroclaw-labs/zeroclaw","slug":"gemini-api-error","errorCode":null,"errorMessage":"Gemini API error: {}","messagePattern":"Gemini API error: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/gemini.rs","lineNumber":1366,"sourceCode":"                        project.as_deref(),\n                        oauth_token.as_deref(),\n                    )?\n                    .send()\n                    .await?;\n            } else {\n                anyhow::bail!(\"Gemini API error ({status}): {error_text}\");\n            }\n        }\n\n        if !response.status().is_success() {\n            let status = response.status();\n            let error_text = response.text().await.unwrap_or_default();\n            anyhow::bail!(\"Gemini API error ({status}): {error_text}\");\n        }\n\n        let result: GenerateContentResponse = response.json().await?;\n        if let Some(err) = &result.error {\n            anyhow::bail!(\"Gemini API error: {}\", err.message);\n        }\n        let result = result.into_effective_response();\n        if let Some(err) = result.error {\n            anyhow::bail!(\"Gemini API error: {}\", err.message);\n        }\n\n        let usage = result\n            .usage_metadata\n            .and_then(Self::token_usage_from_metadata);\n\n        let text = result\n            .candidates\n            .and_then(|c| c.into_iter().next())\n            .and_then(|c| c.content)\n            .and_then(|c| c.effective_text())\n            .ok_or_else(|| {\n                ::zeroclaw_log::record!(\n                    ERROR,","sourceCodeStart":1348,"sourceCodeEnd":1384,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/gemini.rs#L1348-L1384","documentation":"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.","triggerScenarios":"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.","commonSituations":"API key created with browser referrer restrictions then used server-side; safety-filter blocks on borderline prompts; partially rolled-out model endpoints.","solutions":["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","Log err.message verbatim for support escalation","Retry only when the message clearly indicates a transient condition"],"exampleFix":"// after - surface the in-band message for routing decisions\nif let Err(e) = provider.chat_with_system(None, prompt, model, None).await {\n    let msg = e.to_string();\n    if msg.contains(\"API_KEY_INVALID\") {\n        return Err(anyhow::anyhow!(\"recreate GEMINI_API_KEY without referrer restrictions\"));\n    }\n    return Err(e);\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight: catch restricted keys before chat calls\nasync fn gemini_key_unrestricted() -> anyhow::Result<()> {\n    // a 1-token test request surfaces API_KEY_INVALID early\n    Ok(())\n}","typeGuard":"fn is_gemini_in_band_error(e: &anyhow::Error) -> bool {\n    e.to_string().starts_with(\"Gemini API error: \")\n}","tryCatchPattern":"if let Err(e) = provider.chat_with_system(None, prompt, model, None).await {\n    let msg = e.to_string();\n    if msg.contains(\"API_KEY_INVALID\") {\n        return Err(anyhow::anyhow!(\"recreate the key without referrer/IP restrictions\"));\n    }\n    if msg.contains(\"safety\") || msg.contains(\"block\") {\n        return Err(anyhow::anyhow!(\"input tripped a content filter: {msg}\"));\n    }\n    return Err(e);\n}","preventionTips":["Create server-side API keys without HTTP referrer restrictions","Run a smoke-test prompt at startup to fail fast on in-band auth errors","Log err.message verbatim; it is the only diagnostic you get on a 200","Do not blind-retry in-band policy errors - they are deterministic"],"tags":["rust","zeroclaw","gemini","in-band-error","api-key-restriction","content-filter"],"backgroundTag":"llm-api-in-band-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}