{"record":{"id":"7408600d46005e4b","repo":"zeroclaw-labs/zeroclaw","slug":"device-code-expired","errorCode":null,"errorMessage":"Device code expired","messagePattern":"Device code expired","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/auth/gemini_oauth.rs","lineNumber":310,"sourceCode":"                refresh_token: token_response.refresh_token,\n                id_token: token_response.id_token,\n                expires_at,\n                token_type: token_response.token_type.or_else(|| Some(\"Bearer\".into())),\n                scope: token_response.scope,\n            });\n        }\n\n        if let Ok(err) = serde_json::from_str::<OAuthErrorResponse>(&body) {\n            match err.error.as_str() {\n                \"authorization_pending\" => {}\n                \"slow_down\" => {\n                    tokio::time::sleep(Duration::from_secs(5)).await;\n                }\n                \"access_denied\" => {\n                    anyhow::bail!(\"User denied authorization\");\n                }\n                \"expired_token\" => {\n                    anyhow::bail!(\"Device code expired\");\n                }\n                _ => {\n                    anyhow::bail!(\n                        \"Google OAuth error: {} - {}\",\n                        err.error,\n                        err.error_description.unwrap_or_default()\n                    );\n                }\n            }\n        }\n    }\n}\n\n/// Receive OAuth code via loopback callback OR manual stdin input.\n/// If the callback server can't receive the redirect (e.g., remote/headless environment),\n/// the user can paste the full callback URL or just the code.\npub async fn receive_loopback_code(expected_state: &str, timeout: Duration) -> Result<String> {\n    ::zeroclaw_log::scope!(","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/auth/gemini_oauth.rs#L292-L328","documentation":"During the device-code poll loop, the Google token endpoint returned error=expired_token: the device_code/user_code pair is no longer valid on Google's side. This is the server-reported expiry, as opposed to error 661 which is the client's own deadline check. It typically fires when Google's expiry for the code is shorter than the locally assumed window, or a poll lands just past the real expiry.","triggerScenarios":"poll_device_code_tokens polls with grant_type=urn:ietf:params:oauth:grant-type:device_code after Google has already expired the device code — the user authorized too late, or polling continued past the provider's expiry while the local deadline (expires_in, defaulted to 1800s when absent) had not yet passed.","commonSituations":"User approves right at the edge of the code lifetime and the next poll crosses it; Google issues a shorter expires_in than the client's fallback default; clock skew between client and server.","solutions":["Re-run auth login --device-code and complete approval well within the code lifetime","Treat any 'Device code expired' message the same as the deadline expiry: the only recovery is a new device code"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match poll_device_code_tokens(client, id, secret, &device).await {\n    Ok(tokens) => tokens,\n    Err(e) if e.to_string() == \"Device code expired\" => {\n        let device = start_device_code_flow(client, id).await?;\n        poll_device_code_tokens(client, id, secret, &device).await?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Complete approval well inside the code lifetime instead of near the edge","Watch for the server-reported expiry when polling long-running flows"],"tags":["oauth","device-code","gemini","expiry","rust"],"backgroundTag":"device-code-expired","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}