zeroclaw-labs/zeroclaw · error
Device code expired before authorization was completed
Error message
Device code expired before authorization was completed
What it means
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.
Source
Thrown at crates/zeroclaw-providers/src/auth/gemini_oauth.rs:260
user_code,
verification_uri: verification_url,
expires_in: device_response.expires_in.unwrap_or(1800),
interval: device_response.interval.unwrap_or(5),
})
}
pub async fn poll_device_code_tokens(
client: &Client,
client_id: &str,
client_secret: &str,
device: &DeviceCodeStart,
) -> Result<TokenSet> {
let deadline = std::time::Instant::now() + Duration::from_secs(device.expires_in);
let interval = Duration::from_secs(device.interval.max(5));
loop {
if std::time::Instant::now() > deadline {
anyhow::bail!("Device code expired before authorization was completed");
}
tokio::time::sleep(interval).await;
let form = [
("client_id", client_id),
("client_secret", client_secret),
("device_code", device.device_code.as_str()),
("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
];
let response = client
.post(GOOGLE_OAUTH_TOKEN_URL)
.form(&form)
.send()
.await
.context("Failed to poll device code")?;
View on GitHub (pinned to 88bb9c8533)
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
Defensive patterns
Strategy: retry
Validate before calling
// Restart the whole flow when polling runs out: a fresh device code is the only recovery.
// Before polling, make sure a human (or automation) is ready to approve within expires_in.
println!("Visit {} within {}s and enter {}", device.verification_uri, device.expires_in, device.user_code); Try / catch
match poll_device_code_tokens(client, id, secret, &device).await {
Ok(tokens) => tokens,
Err(e) if e.to_string().contains("expired before authorization") => {
// deadline passed without approval: issue a new device code and poll again
let device = start_device_code_flow(client, id).await?;
poll_device_code_tokens(client, id, secret, &device).await?
}
Err(e) => return Err(e),
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Device code expired
- Google device code request failed ({}): {}
- User denied authorization
- xAI device-code flow timed out before authorization complete
- xAI device-code expired
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/0d207c867e1342cd.
Report an issue: GitHub.