{"record":{"id":"0983f67dbd3f2ed1","repo":"zeroclaw-labs/zeroclaw","slug":"google-device-code-request-failed","errorCode":null,"errorMessage":"Google device code request failed ({}): {}","messagePattern":"Google device code request failed \\((.+?)\\): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/auth/gemini_oauth.rs","lineNumber":230,"sourceCode":"        .send()\n        .await\n        .context(\"Failed to start device code flow\")?;\n\n    let status = response.status();\n    let body = response\n        .text()\n        .await\n        .context(\"Failed to read device code response\")?;\n\n    if !status.is_success() {\n        if let Ok(err) = serde_json::from_str::<OAuthErrorResponse>(&body) {\n            anyhow::bail!(\n                \"Google device code error: {} - {}\",\n                err.error,\n                err.error_description.unwrap_or_default()\n            );\n        }\n        anyhow::bail!(\"Google device code request failed ({}): {}\", status, body);\n    }\n\n    let device_response: DeviceCodeResponse =\n        serde_json::from_str(&body).context(\"Failed to parse device code response\")?;\n\n    let user_code = device_response.user_code;\n    let verification_url = device_response.verification_url;\n\n    Ok(DeviceCodeStart {\n        device_code: device_response.device_code,\n        verification_uri_complete: Some(format!(\"{verification_url}?user_code={user_code}\")),\n        user_code,\n        verification_uri: verification_url,\n        expires_in: device_response.expires_in.unwrap_or(1800),\n        interval: device_response.interval.unwrap_or(5),\n    })\n}\n","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/auth/gemini_oauth.rs#L212-L248","documentation":"Thrown by start_device_code_flow when POST https://oauth2.googleapis.com/device/code returns a non-success HTTP status AND the response body does not parse as a Google OAuth error JSON ({\"error\":..., \"error_description\":...}). It is the fallback branch: well-formed Google OAuth errors take the separate 'Google device code error' path, so this message means an opaque or non-JSON failure body. The exact status code and raw body are embedded in the message.","triggerScenarios":"Running the Gemini device-code flow (auth login --model-provider gemini --device-code, which calls start_device_code_flow(client, client_id)) and the device-code endpoint replies non-2xx with a non-OAuth-JSON body: an HTML block page from a corporate proxy (403/502), a plain-text 429 rate-limit response, a Google outage page, or a malformed/typo'd client_id that makes the endpoint emit an unexpected payload.","commonSituations":"Typo in oauth_client_id under [providers.models.gemini.<profile>]; a Google Cloud OAuth app that is misconfigured or still in a publishing state that rejects the device flow; egress through a proxy that intercepts accounts.google.com/oauth2.googleapis.com; hitting token-endpoint quota after repeated logins.","solutions":["Read the embedded status and body in the message: a 4xx points to client credentials, a 5xx/429 to Google or the network path","Verify oauth_client_id (and the alias table [providers.models.gemini.<profile>]) matches a valid Google Cloud OAuth client that permits the device flow","If the body shows a proxy/interceptor page, bypass the proxy or allowlist oauth2.googleapis.com and retry","On 429, wait for the quota window to reset before re-running the login","Reproduce with curl -X POST https://oauth2.googleapis.com/device/code -d client_id=... -d scope=... to see the raw response"],"exampleFix":"# before (config.toml) — truncated/wrong client id\n[providers.models.gemini.default]\noauth_client_id = \"1234567890\"\noauth_client_secret = \"...\"\n\n# after — full, verified Google Cloud OAuth client id\n[providers.models.gemini.default]\noauth_client_id = \"1234567890-abcdefghijklmnop.apps.googleusercontent.com\"\noauth_client_secret = \"GOCSPX-...\"","handlingStrategy":"try-catch","validationCode":"// Verify the alias carries a non-empty client id before starting the device flow.\nlet alias = config.providers.models.gemini.get(profile)\n    .ok_or_else(|| anyhow::anyhow!(\"missing [providers.models.gemini.{profile}]\"))?;\nif alias.oauth_client_id.as_deref().map_or(true, |s| s.trim().is_empty()) {\n    anyhow::bail!(\"oauth_client_id is empty; fix the alias config before device-code login\");\n} else {\n    start_device_code_flow(client, alias.oauth_client_id.as_deref().unwrap()).await?;\n}","typeGuard":null,"tryCatchPattern":"match gemini_oauth::start_device_code_flow(client, client_id).await {\n    Ok(device) => device,\n    Err(e) => {\n        let msg = e.to_string();\n        if msg.contains(\"(429\") || msg.contains(\"(5\") {\n            // rate limit / server side: safe to retry after a pause\n            tokio::time::sleep(std::time::Duration::from_secs(30)).await;\n            gemini_oauth::start_device_code_flow(client, client_id).await?\n        } else {\n            // 4xx with a non-OAuth body: credentials or proxy problem, do not retry blindly\n            return Err(e.context(\"device-code endpoint rejected the request; check client_id and network path\"));\n        }\n    }\n}","preventionTips":["Keep oauth_client_id/oauth_client_secret in [providers.models.gemini.<profile>] in sync with the Google Cloud OAuth app","Smoke-test the endpoint with curl when the body looks like a proxy page","Treat 429/5xx statuses as retryable and everything else as configuration failure"],"tags":["oauth","gemini","device-code","http-status","google","rust"],"backgroundTag":"oauth-endpoint-http-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}