{"record":{"id":"450bbfcfc43d173b","repo":"zeroclaw-labs/zeroclaw","slug":"xai-device-code-authorization-was-denied","errorCode":null,"errorMessage":"xAI device-code authorization was denied","messagePattern":"xAI device-code authorization was denied","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/zeroclaw-providers/src/auth/xai_oauth.rs","lineNumber":308,"sourceCode":"            .send()\n            .await\n            .context(\"Failed polling xAI device-code token endpoint\")?;\n\n        if response.status().is_success() {\n            return parse_token_response(response).await;\n        }\n\n        let status = response.status();\n        let text = response.text().await.unwrap_or_default();\n        if let Ok(err) = serde_json::from_str::<OAuthErrorResponse>(&text) {\n            match err.error.as_str() {\n                \"authorization_pending\" => continue,\n                \"slow_down\" => {\n                    interval_secs = interval_secs.saturating_add(5);\n                    continue;\n                }\n                \"access_denied\" | \"authorization_denied\" => {\n                    anyhow::bail!(\"xAI device-code authorization was denied\")\n                }\n                \"expired_token\" => anyhow::bail!(\"xAI device-code expired\"),\n                _ => anyhow::bail!(\n                    \"xAI device-code polling failed ({status}): {}\",\n                    err.error_description.unwrap_or(err.error)\n                ),\n            }\n        }\n        anyhow::bail!(\"xAI device-code polling failed ({status}): {text}\");\n    }\n}\n\nasync fn parse_token_response(response: reqwest::Response) -> Result<TokenSet> {\n    let status = response.status();\n    let body = response.text().await.unwrap_or_default();\n    if !status.is_success() {\n        if let Ok(err) = serde_json::from_str::<OAuthErrorResponse>(&body) {\n            anyhow::bail!(","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/auth/xai_oauth.rs#L290-L326","documentation":"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.","triggerScenarios":"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.","commonSituations":"User changes their mind mid-login; wrong account signed in; consent screen shows unexpected scopes (grok-cli:access, api:access).","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"fn is_user_denial(e: &anyhow::Error) -> bool {\n    e.to_string().contains(\"device-code authorization was denied\")\n}","tryCatchPattern":"match poll_device_code_tokens(&client, &ep, &device).await {\n    Ok(t) => t,\n    Err(e) if is_user_denial(&e) => {\n        return Err(anyhow!(\"login cancelled by user\")); // expected cancellation: exit cleanly\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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"],"tags":["oauth","xai","device-code","consent","rust"],"backgroundTag":"oauth-access-denied","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}