zeroclaw-labs/zeroclaw · error · anyhow::Error
xAI device-code expired
Error message
xAI device-code expired
What it means
The xAI token endpoint returned `expired_token` during device-code polling: the device_code's assigned lifetime ran out on the server side, typically because approval came too late or polling resumed after a process sleep. Distinct from the client-side elapsed-time deadline, this is the provider's verdict that the code is dead.
Source
Thrown at crates/zeroclaw-providers/src/auth/xai_oauth.rs:310
.context("Failed polling xAI device-code token endpoint")?;
if response.status().is_success() {
return parse_token_response(response).await;
}
let status = response.status();
let text = response.text().await.unwrap_or_default();
if let Ok(err) = serde_json::from_str::<OAuthErrorResponse>(&text) {
match err.error.as_str() {
"authorization_pending" => continue,
"slow_down" => {
interval_secs = interval_secs.saturating_add(5);
continue;
}
"access_denied" | "authorization_denied" => {
anyhow::bail!("xAI device-code authorization was denied")
}
"expired_token" => anyhow::bail!("xAI device-code expired"),
_ => anyhow::bail!(
"xAI device-code polling failed ({status}): {}",
err.error_description.unwrap_or(err.error)
),
}
}
anyhow::bail!("xAI device-code polling failed ({status}): {text}");
}
}
async fn parse_token_response(response: reqwest::Response) -> Result<TokenSet> {
let status = response.status();
let body = response.text().await.unwrap_or_default();
if !status.is_success() {
if let Ok(err) = serde_json::from_str::<OAuthErrorResponse>(&body) {
anyhow::bail!(
"xAI OAuth token request failed ({status}): {}",
err.error_description.unwrap_or(err.error)View on GitHub (pinned to 88bb9c8533)
Solutions
- Start a new flow with `start_device_code_flow` — expired codes cannot be revived
- Complete approval promptly after the user_code is displayed
- Avoid machine sleep while a device flow is in progress
Defensive patterns
Strategy: retry
Try / catch
match poll_device_code_tokens(&client, &ep, &device).await {
Ok(t) => t,
Err(e) if e.to_string().contains("xAI device-code expired") => {
let device = start_device_code_flow(&client, &dev_ep).await?; // expired codes cannot be revived
poll_device_code_tokens(&client, &ep, &device).await?
}
Err(e) => return Err(e),
} Prevention
- Complete approval within expires_in of the device start
- Prevent machine sleep during an active flow
- Handle expired_token and the client-side timeout with the same remedy: a fresh start_device_code_flow
When it happens
Trigger: User approves at the verification page after expires_in elapsed; the machine slept mid-flow so polls resume against an expired device_code.
Common situations: Laptop sleep during login; long verification-page delays; user returning to an old terminal session.
Related errors
- Device code expired before authorization was completed
- Device code expired
- xAI device-code start failed ({status}): {body}
- xAI device-code flow timed out before authorization complete
- xAI device-code authorization was denied
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/c1dccea58afe9e8d.
Report an issue: GitHub.