{"record":{"id":"0d207c867e1342cd","repo":"zeroclaw-labs/zeroclaw","slug":"device-code-expired-before-authorization-was-compl","errorCode":null,"errorMessage":"Device code expired before authorization was completed","messagePattern":"Device code expired before authorization was completed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/auth/gemini_oauth.rs","lineNumber":260,"sourceCode":"        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\npub async fn poll_device_code_tokens(\n    client: &Client,\n    client_id: &str,\n    client_secret: &str,\n    device: &DeviceCodeStart,\n) -> Result<TokenSet> {\n    let deadline = std::time::Instant::now() + Duration::from_secs(device.expires_in);\n    let interval = Duration::from_secs(device.interval.max(5));\n\n    loop {\n        if std::time::Instant::now() > deadline {\n            anyhow::bail!(\"Device code expired before authorization was completed\");\n        }\n\n        tokio::time::sleep(interval).await;\n\n        let form = [\n            (\"client_id\", client_id),\n            (\"client_secret\", client_secret),\n            (\"device_code\", device.device_code.as_str()),\n            (\"grant_type\", \"urn:ietf:params:oauth:grant-type:device_code\"),\n        ];\n\n        let response = client\n            .post(GOOGLE_OAUTH_TOKEN_URL)\n            .form(&form)\n            .send()\n            .await\n            .context(\"Failed to poll device code\")?;\n","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/auth/gemini_oauth.rs#L242-L278","documentation":"poll_device_code_tokens computes a local deadline of now + device.expires_in seconds (Google default 1800, applied via unwrap_or(1800)) before entering its polling loop. If wall-clock time passes that deadline while the token endpoint keeps answering anything other than success, the function bails with this message. It means the user never completed authorization at the verification URL within the code's lifetime.","triggerScenarios":"Calling poll_device_code_tokens (from auth login --device-code for gemini) and letting device.expires_in elapse: the user never visits device.verification_uri, never enters device.user_code, or approves only after the 30-minute window closes. Every loop iteration still receives authorization_pending from Google, so the local deadline is what fires.","commonSituations":"User starts the login, is interrupted, and approves hours later; an automation script starts the device flow long before a human is ready to approve; expires_in was shortened by the provider while the client assumed the 1800s default.","solutions":["Re-run auth login --device-code to get a fresh device code and approve promptly","Use the printed verification_uri_complete link (verification_url?user_code=...) so the code is pre-filled and approval takes seconds","Keep the flow that starts the device code and the human approval close together in scripts"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Restart the whole flow when polling runs out: a fresh device code is the only recovery.\n// Before polling, make sure a human (or automation) is ready to approve within expires_in.\nprintln!(\"Visit {} within {}s and enter {}\", device.verification_uri, device.expires_in, device.user_code);","typeGuard":null,"tryCatchPattern":"match poll_device_code_tokens(client, id, secret, &device).await {\n    Ok(tokens) => tokens,\n    Err(e) if e.to_string().contains(\"expired before authorization\") => {\n        // deadline passed without approval: issue a new device code and poll again\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":["Open verification_uri_complete immediately so approval takes seconds","Do not start the device flow during a step that can stall for hours","Automate approval promptly when running headless setups"],"tags":["oauth","device-code","gemini","timeout","expiry","rust"],"backgroundTag":"device-code-expired","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}