{"record":{"id":"c6307f535b3b24f0","repo":"zeroclaw-labs/zeroclaw","slug":"xai-oauth-token-request-failed-status","errorCode":null,"errorMessage":"xAI OAuth token request failed ({status}): {}","messagePattern":"xAI OAuth token request failed \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/auth/xai_oauth.rs","lineNumber":326,"sourceCode":"                    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!(\n                \"xAI OAuth token request failed ({status}): {}\",\n                err.error_description.unwrap_or(err.error)\n            );\n        }\n        anyhow::bail!(\"xAI OAuth token request failed ({status}): {body}\");\n    }\n\n    let parsed: TokenResponse =\n        serde_json::from_str(&body).context(\"Failed to parse xAI OAuth token response\")?;\n    let expires_at = parsed\n        .expires_in\n        .map(|secs| Utc::now() + chrono::Duration::seconds(secs))\n        .or_else(|| derive_expires_at_from_jwt(&parsed.access_token));\n\n    Ok(TokenSet {\n        access_token: parsed.access_token,\n        refresh_token: parsed.refresh_token,\n        id_token: parsed.id_token,","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/auth/xai_oauth.rs#L308-L344","documentation":"xAI's `parse_token_response` — shared by `exchange_code_for_tokens`, `refresh_access_token`, and device polling — got a non-2xx whose body parsed as a standard OAuth error document; the provider's `error_description` (falling back to `error`) is embedded with the status. This is the informative variant: the provider told you exactly what failed.","triggerScenarios":"`invalid_grant` when the authorization code expired or was replayed, or when the PKCE pair mismatches (xAI revalidates `code_challenge` and `code_challenge_method` at the token endpoint); a refresh token that was revoked or rotated; `invalid_client`.","commonSituations":"Exchanging a code twice after a crash; a verifier from a different PkceState than the challenge sent in the authorize URL; `restore_pkce_state` rebuilt with the wrong saved verifier; old refresh token superseded by a login elsewhere.","solutions":["Map the embedded code: `invalid_grant` → restart login for a fresh code or refresh token; check the PKCE pair when exchanging","Persist the PkceState before opening the browser and restore it verbatim with `restore_pkce_state`","Ensure `redirect_uri` is exactly `http://127.0.0.1:56121/callback` (XAI_OAUTH_REDIRECT_URI)","Write the new refresh token to the store immediately after every refresh (rotation invalidates the old one)"],"exampleFix":"// before: new random PKCE at exchange time — challenge unknown to xAI\nlet pkce = generate_pkce_state();\nlet tokens = exchange_code_for_tokens(&client, &token_ep, &code, &pkce).await?; // invalid_grant\n\n// after: persist the original verifier+state and restore them exactly\nlet pkce = restore_pkce_state(saved_code_verifier, saved_state);\nlet tokens = exchange_code_for_tokens(&client, &token_ep, &code, &pkce).await?;","handlingStrategy":"try-catch","validationCode":"// before opening the browser, make sure the PKCE pair is restorable\nlet pkce = generate_pkce_state();\nsave_pkce_to_state(&pkce).await?; // verifier + state survive a crash\nlet url = build_authorize_url(&disc.authorization_endpoint, &pkce);","typeGuard":"fn is_xai_invalid_grant(e: &anyhow::Error) -> bool {\n    let s = e.to_string();\n    s.contains(\"xAI OAuth token request failed\") && s.contains(\"invalid_grant\")\n}","tryCatchPattern":"let tokens = match refresh_access_token(&client, &refresh).await {\n    Ok(t) => t,\n    Err(e) if is_xai_invalid_grant(&e) => run_interactive_login().await?,\n    Err(e) => return Err(e),\n};","preventionTips":["Persist the PkceState (verifier + state) before opening the browser; restore it verbatim with restore_pkce_state","Exchange the authorization code once, immediately","Rotate the stored refresh token right after every refresh"],"tags":["oauth","xai","token-endpoint","pkce","http","rust"],"backgroundTag":"oauth-token-endpoint-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}