{"record":{"id":"0160e86876a42da0","repo":"zeroclaw-labs/zeroclaw","slug":"err-desc","errorCode":null,"errorMessage":"{err}: {desc}","messagePattern":"\\{err\\}: \\{desc\\}","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/zeroclaw-providers/src/auth/xai_oauth.rs","lineNumber":410,"sourceCode":"        body\n    );\n    let _ = stream.write_all(response.as_bytes()).await;\n    Ok(code)\n}\n\npub fn parse_code_from_redirect(input: &str, expected_state: Option<&str>) -> Result<String> {\n    let trimmed = input.trim();\n    if trimmed.is_empty() {\n        anyhow::bail!(\"No xAI OAuth code provided\");\n    }\n    let query = trimmed.split_once('?').map_or(trimmed, |(_, query)| query);\n    let params = parse_query_params(query);\n    if let Some(err) = params.get(\"error\") {\n        let desc = params\n            .get(\"error_description\")\n            .cloned()\n            .unwrap_or_else(|| \"xAI OAuth authorization failed\".to_string());\n        anyhow::bail!(\"{err}: {desc}\");\n    }\n    if let Some(expected) = expected_state {\n        let actual = params\n            .get(\"state\")\n            .ok_or_else(|| anyhow::Error::msg(\"xAI OAuth callback missing state parameter\"))?;\n        if actual != expected {\n            anyhow::bail!(\"xAI OAuth state mismatch\");\n        }\n    }\n    if let Some(code) = params.get(\"code\")\n        && !code.trim().is_empty()\n    {\n        return Ok(code.trim().to_string());\n    }\n    if expected_state.is_none() && !trimmed.contains('=') && !trimmed.contains('?') {\n        return Ok(trimmed.to_string());\n    }\n    anyhow::bail!(\"xAI OAuth callback missing code parameter\")","sourceCodeStart":392,"sourceCodeEnd":428,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/auth/xai_oauth.rs#L392-L428","documentation":"The xAI callback query contained an `error` parameter, so `parse_code_from_redirect` fails with `{error}: {error_description}` (default description: \"xAI OAuth authorization failed\"). This is the IdP reporting that authorization itself failed — no code was ever issued. The classic body is `access_denied: user cancelled...`, but any redirected OAuth error surfaces here.","triggerScenarios":"The user cancels at the xAI consent page and the redirect carries `error=access_denied&error_description=...`; scope or client errors redirect with error params instead of a code.","commonSituations":"User backs out of consent; the account lacks permission for a requested scope (XAI_OAUTH_SCOPE includes grok-cli:access and api:access); client policy changes at the IdP.","solutions":["Read the two parts: `access_denied` is user cancellation — offer to restart; other codes indicate config or provider changes","Restart the flow via `build_authorize_url` with the standard XAI_OAUTH_SCOPE","For scope failures, verify the account has Grok/API access enabled"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"fn classify_callback_error(e: &anyhow::Error) -> &'static str {\n    let s = e.to_string();\n    if s.starts_with(\"access_denied\") { \"user-cancelled\" } else { \"authorization-config-error\" }\n}","tryCatchPattern":"match parse_code_from_redirect(path, Some(&pkce.state)) {\n    Ok(code) => exchange(code),\n    Err(e) if e.to_string().starts_with(\"access_denied\") => prompt_retry_login().await,\n    Err(e) if e.to_string().contains(\": \") => return Err(e), // provider-redirected error: surface it\n    Err(e) => return Err(e),\n}","preventionTips":["Classify error-param redirects as user cancellation vs configuration error before retrying","Verify the account holds grok-cli:access/api:access scopes when denial repeats","Restart only via build_authorize_url with the standard XAI_OAUTH_SCOPE"],"tags":["oauth","xai","callback","consent","rust"],"backgroundTag":"oauth-authorization-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}