zeroclaw-labs/zeroclaw · error · anyhow::Error
xAI device-code flow timed out before authorization complete
Error message
xAI device-code flow timed out before authorization completed
What it means
`poll_device_code_tokens` checks elapsed time against `device.expires_in` (the lifetime xAI assigned when the flow started) and bails once the budget is spent without a successful token response. This is a client-side deadline, distinct from the server's `expired_token` verdict: the user simply did not complete authorization in time.
Source
Thrown at crates/zeroclaw-providers/src/auth/xai_oauth.rs:276
.map(|uri| require_trusted_endpoint(uri, "complete verification URI"))
.transpose()?,
expires_in: parsed.expires_in,
interval: parsed.interval.unwrap_or(5).max(1),
})
}
pub async fn poll_device_code_tokens(
client: &Client,
token_endpoint: &str,
device: &DeviceCodeStart,
) -> Result<TokenSet> {
let token_endpoint = require_trusted_endpoint(token_endpoint, "token endpoint")?;
let started = Instant::now();
let mut interval_secs = device.interval.max(1);
loop {
if started.elapsed() > Duration::from_secs(device.expires_in) {
anyhow::bail!("xAI device-code flow timed out before authorization completed");
}
tokio::time::sleep(Duration::from_secs(interval_secs)).await;
let form = [
("grant_type", XAI_DEVICE_CODE_GRANT_TYPE),
("device_code", device.device_code.as_str()),
("client_id", XAI_OAUTH_CLIENT_ID),
];
let response = client
.post(&token_endpoint)
.form(&form)
.send()
.await
.context("Failed polling xAI device-code token endpoint")?;
if response.status().is_success() {View on GitHub (pinned to 88bb9c8533)
Solutions
- Restart the flow: call `start_device_code_flow` again for a fresh device_code and expiry window
- Use `verification_uri_complete` when present to skip manual code entry
- Display user_code immediately and prompt the user to open the verification URI right away
Example fix
// after: treat the deadline as restartable
match poll_device_code_tokens(&client, &disc.token_endpoint, &device).await {
Ok(t) => t,
Err(e) if e.to_string().contains("timed out before authorization") => {
let device = start_device_code_flow(&client, &disc.device_authorization_endpoint).await?;
poll_device_code_tokens(&client, &disc.token_endpoint, &device).await?
}
Err(e) => return Err(e),
} Defensive patterns
Strategy: retry
Try / catch
match poll_device_code_tokens(&client, &disc.token_endpoint, &device).await {
Ok(t) => t,
Err(e) if e.to_string().contains("timed out before authorization") => {
let device = start_device_code_flow(&client, &disc.device_authorization_endpoint).await?;
poll_device_code_tokens(&client, &disc.token_endpoint, &device).await?
}
Err(e) => return Err(e),
} Prevention
- Print user_code and verification_uri immediately after start
- Link verification_uri_complete when present to save entry time
- Never re-poll an expired DeviceCodeStart — always restart the flow
When it happens
Trigger: The user starts login, receives user_code and verification_uri, and never approves before expires_in elapses; polls keep returning authorization_pending until the local clock check trips.
Common situations: Unattended terminal; user steps away; verification page queues or MFA drags on past the window.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Device code expired before authorization was completed
- xAI device-code start failed ({status}): {body}
- xAI device-code authorization was denied
- xAI device-code expired
- xAI device-code polling failed ({status}): {}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/1139e9bd79ae7a96.
Report an issue: GitHub.