{"record":{"id":"96db7567005631ca","repo":"zeroclaw-labs/zeroclaw","slug":"xai-oauth-state-mismatch","errorCode":null,"errorMessage":"xAI OAuth state mismatch","messagePattern":"xAI OAuth state mismatch","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/auth/xai_oauth.rs","lineNumber":417,"sourceCode":"    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\")\n}\n\npub fn extract_account_id_from_jwt(token: &str) -> Option<String> {\n    let payload = decode_jwt_payload(token)?;\n    payload\n        .get(\"email\")\n        .or_else(|| payload.get(\"sub\"))","sourceCodeStart":399,"sourceCodeEnd":435,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/auth/xai_oauth.rs#L399-L435","documentation":"CSRF guard of the xAI flow: `parse_code_from_redirect` compared the callback's `state` to the `expected_state` and they differ. The xAI parser hard-requires state whenever an expectation is passed — a missing state is its own separate error — so this specifically means a present-but-different value. Used by `receive_loopback_code` (loopback 127.0.0.1:56121) and direct callers.","triggerScenarios":"`parse_code_from_redirect(\"/callback?code=abc&state=bad\", Some(\"xyz\"))`; via the loopback listener, a stale browser redirect from an earlier login attempt lands after a new flow started on the same fixed port.","commonSituations":"Concurrent or restarted logins sharing port 56121; `restore_pkce_state` called with a guessed or wrong saved `state` value across a process restart; stale auth.x.ai tabs.","solutions":["Restart the flow so one PkceState builds the authorize URL and validates the callback","Close old tabs on auth.x.ai that will redirect with the previous state","When restoring across process restarts, persist and reload the exact state used in the authorize URL","Serialize logins so only one flow owns the loopback port at a time"],"exampleFix":"// before: restored state does not match what was sent\nlet pkce = restore_pkce_state(code_verifier, \"guessed-state\".into());\n// callback state=actual -> \"xAI OAuth state mismatch\"\n\n// after: persist the state you embedded and restore it verbatim\nlet pkce = restore_pkce_state(saved_code_verifier, saved_state);","handlingStrategy":"validation","validationCode":"fn xai_state_matches(input: &str, expected: &str) -> bool {\n    input.split_once('?').map_or(false, |(_, q)| {\n        q.split('&').any(|pair| pair == format!(\"state={}\", expected))\n    })\n}\n\nif xai_state_matches(path, &pkce.state) {\n    let code = parse_code_from_redirect(path, Some(&pkce.state))?;\n}","typeGuard":null,"tryCatchPattern":"match parse_code_from_redirect(path, Some(&pkce.state)) {\n    Ok(code) => exchange(code),\n    Err(e) if e.to_string().contains(\"xAI OAuth state mismatch\") => restart_login_flow().await,\n    Err(e) => return Err(e),\n}","preventionTips":["Persist and restore the exact state used in the authorize URL (restore_pkce_state)","Use one PkceState per attempt, end to end","Close stale auth.x.ai tabs before restarting login on port 56121"],"tags":["oauth","xai","csrf","state","rust"],"backgroundTag":"oauth-state-mismatch","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}