{"record":{"id":"7698da0cc3327341","repo":"zeroclaw-labs/zeroclaw","slug":"gemini-cli-oauth-refresh-failed-http-status","errorCode":null,"errorMessage":"Gemini CLI OAuth refresh failed (HTTP {status}): {body}","messagePattern":"Gemini CLI OAuth refresh failed \\(HTTP (.+?)\\): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/gemini.rs","lineNumber":392,"sourceCode":"                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                    .with_attrs(::serde_json::json!({\n                        \"oauth_provider\": \"gemini_cli\",\n                        \"phase\": \"refresh_request\",\n                        \"error\": format!(\"{}\", error),\n                    })),\n                \"gemini: CLI OAuth refresh request failed\"\n            );\n            anyhow::Error::msg(format!(\"Gemini CLI OAuth refresh request failed: {error}\"))\n        })?;\n\n    let status = response.status();\n    let body = response\n        .text()\n        .unwrap_or_else(|_| \"<failed to read response body>\".to_string());\n\n    if !status.is_success() {\n        anyhow::bail!(\"Gemini CLI OAuth refresh failed (HTTP {status}): {body}\");\n    }\n\n    #[derive(Deserialize)]\n    struct TokenResponse {\n        access_token: Option<String>,\n        expires_in: Option<i64>,\n    }\n\n    let parsed: TokenResponse = serde_json::from_str(&body).map_err(|_| {\n        ::zeroclaw_log::record!(\n            ERROR,\n            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                .with_attrs(::serde_json::json!({\"oauth_provider\": \"gemini_cli\"})),\n            \"gemini: CLI OAuth refresh response is not valid JSON\"\n        );\n        anyhow::Error::msg(\"Gemini CLI OAuth refresh response is not valid JSON\")\n    })?;","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/gemini.rs#L374-L410","documentation":"refresh_gemini_cli_token POSTs the stored refresh token (plus optional client_id/client_secret) to Google's OAuth token endpoint. A non-2xx - most often 400 invalid_grant - is surfaced with the HTTP status and Google's response body so the exact OAuth error is visible.","triggerScenarios":"The refresh token was revoked (Google account password change, security reset, revoking Gemini CLI app access in account settings), expired, or does not match the client_id/client_secret sent with the request.","commonSituations":"Long-lived setups where Google expired the token; user revoked app access at myaccount.google.com/permissions; multiple Gemini CLI versions overwriting ~/.gemini credentials with different client ids; large system clock skew.","solutions":["Re-run `gemini` in a terminal and complete OAuth so a fresh oauth_creds.json with a new refresh token is written","Check myaccount.google.com/permissions and re-allow the Gemini CLI app if it was revoked","If you use managed auth profiles, refresh or re-import the profile","Verify the system clock (NTP) - significant skew can make Google reject refresh requests"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// Detect a dead refresh token before it hits a chat request\nasync fn refresh_probe(token_state: &OAuthTokenState) -> bool {\n    match refresh_gemini_cli_token(\n        token_state.refresh_token.as_deref().unwrap_or(\"\"),\n        token_state.client_id.as_deref(),\n        token_state.client_secret.as_deref(),\n    ) {\n        Ok(_) => true,\n        Err(_) => false, // schedule re-login instead of failing mid-chat\n    }\n}","typeGuard":null,"tryCatchPattern":"match provider.chat(/* ... */).await {\n    Ok(resp) => Ok(resp),\n    Err(e) if e.to_string().contains(\"OAuth refresh failed\") => {\n        // invalid_grant is terminal for this credential:\n        // prompt the user to re-run `gemini` and retry once after re-auth\n        prompt_relogin_and_retry_once().await\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Re-run `gemini` login on a schedule for long-lived services using CLI OAuth","Do not auto-retry invalid_grant - it never succeeds without re-consent","Keep client_id/client_secret consistent with the CLI version that minted the token","Prefer API-key auth for unattended servers"],"tags":["gemini","oauth","refresh-token","google","rust"],"backgroundTag":"oauth-refresh-token-rejected","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}