zeroclaw-labs/zeroclaw · error
Device code expired
Error message
Device code expired
What it means
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.
Source
Thrown at crates/zeroclaw-providers/src/auth/gemini_oauth.rs:310
refresh_token: token_response.refresh_token,
id_token: token_response.id_token,
expires_at,
token_type: token_response.token_type.or_else(|| Some("Bearer".into())),
scope: token_response.scope,
});
}
if let Ok(err) = serde_json::from_str::<OAuthErrorResponse>(&body) {
match err.error.as_str() {
"authorization_pending" => {}
"slow_down" => {
tokio::time::sleep(Duration::from_secs(5)).await;
}
"access_denied" => {
anyhow::bail!("User denied authorization");
}
"expired_token" => {
anyhow::bail!("Device code expired");
}
_ => {
anyhow::bail!(
"Google OAuth error: {} - {}",
err.error,
err.error_description.unwrap_or_default()
);
}
}
}
}
}
/// Receive OAuth code via loopback callback OR manual stdin input.
/// If the callback server can't receive the redirect (e.g., remote/headless environment),
/// the user can paste the full callback URL or just the code.
pub async fn receive_loopback_code(expected_state: &str, timeout: Duration) -> Result<String> {
::zeroclaw_log::scope!(View on GitHub (pinned to 88bb9c8533)
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
Defensive patterns
Strategy: retry
Try / catch
match poll_device_code_tokens(client, id, secret, &device).await {
Ok(tokens) => tokens,
Err(e) if e.to_string() == "Device code expired" => {
let device = start_device_code_flow(client, id).await?;
poll_device_code_tokens(client, id, secret, &device).await?
}
Err(e) => return Err(e),
} Prevention
- Complete approval well inside the code lifetime instead of near the edge
- Watch for the server-reported expiry when polling long-running flows
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Device code expired before authorization was completed
- Google device code request failed ({}): {}
- User denied authorization
- xAI device-code expired
- OAuth state mismatch
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/7408600d46005e4b.
Report an issue: GitHub.