zeroclaw-labs/zeroclaw · warning · anyhow::Error
xAI device-code authorization was denied
Error message
xAI device-code authorization was denied
What it means
While polling the xAI token endpoint, the response carried the standard OAuth error `access_denied` (or xAI's `authorization_denied`): the user actively rejected consent at the verification page. Polling stops immediately and there is no token to recover. This is an expected user-cancellation path, not a system fault.
Source
Thrown at crates/zeroclaw-providers/src/auth/xai_oauth.rs:308
.send()
.await
.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!(View on GitHub (pinned to 88bb9c8533)
Solutions
- Treat it as user cancellation in UX: exit cleanly or offer to restart the flow
- If denial recurs for a specific account, verify that account is permitted xAI/Grok access
- Restart with `start_device_code_flow` only when the user explicitly wants to retry
Defensive patterns
Strategy: try-catch
Type guard
fn is_user_denial(e: &anyhow::Error) -> bool {
e.to_string().contains("device-code authorization was denied")
} Try / catch
match poll_device_code_tokens(&client, &ep, &device).await {
Ok(t) => t,
Err(e) if is_user_denial(&e) => {
return Err(anyhow!("login cancelled by user")); // expected cancellation: exit cleanly
}
Err(e) => return Err(e),
} Prevention
- Treat denial as cancellation UX, never as a silent retry
- Tell the user which account the verification page expects
- Investigate account permissions when denial repeats unexpectedly
When it happens
Trigger: The user clicks deny or cancel at auth.x.ai while `poll_device_code_tokens` is in its polling loop; the device-code confirmation page is opened with an account that refuses the requested scopes.
Common situations: User changes their mind mid-login; wrong account signed in; consent screen shows unexpected scopes (grok-cli:access, api:access).
Related errors
- User denied authorization
- xAI device-code start failed ({status}): {body}
- xAI device-code flow timed out before authorization complete
- 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/450bbfcfc43d173b.
Report an issue: GitHub.