{"record":{"id":"58411effba0a8550","repo":"Hmbown/CodeWhale","slug":"oauth-callback-state-did-not-match-the-pending-login","errorCode":null,"errorMessage":"OAuth callback state did not match the pending login","messagePattern":"OAuth callback state did not match the pending login","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/oauth.rs","lineNumber":1221,"sourceCode":"        return Ok(CallbackOutcome::Error {\n            error,\n            description,\n            state,\n        });\n    }\n    let code = code\n        .filter(|c| !c.trim().is_empty())\n        .context(\"OAuth callback missing authorization code\")?;\n    let state = state\n        .filter(|s| !s.trim().is_empty())\n        .context(\"OAuth callback missing state\")?;\n    Ok(CallbackOutcome::Success { code, state })\n}\n\npub fn accept_callback(expected_state: &str, outcome: CallbackOutcome) -> Result<String> {\n    match outcome {\n        CallbackOutcome::Success { code, state } => {\n            anyhow::ensure!(\n                state == expected_state,\n                \"OAuth callback state did not match the pending login\"\n            );\n            Ok(code)\n        }\n        CallbackOutcome::Error {\n            error,\n            description,\n            state,\n        } => {\n            if let Some(state) = state {\n                anyhow::ensure!(\n                    state == expected_state,\n                    \"OAuth error callback state did not match the pending login\"\n                );\n            }\n            let detail = description\n                .filter(|text| !text.trim().is_empty())","sourceCodeStart":1203,"sourceCodeEnd":1239,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/oauth.rs#L1203-L1239","documentation":"The browser-based OAuth flow binds a random `state` value to the pending login and requires the callback to echo it back; `accept_callback` compares them. A mismatch means the callback the library received belongs to a different (or forged) login attempt, so it refuses to accept the authorization code — this is the standard CSRF protection for the OAuth redirect flow.","triggerScenarios":"`accept_callback(expected_state, CallbackOutcome::Success { code, state })` receives a `state` that does not equal the `expected_state` recorded when the login started — e.g. two logins interleaved, a stale browser tab replaying an old callback, or a state parameter stripped/rewritten by a redirect.","commonSituations":"Starting a second sign-in before finishing the first, so the old tab's callback no longer matches the new pending state; a proxy or URL rewriter mangling the query string; pasting a callback URL from an earlier attempt.","solutions":["Restart the sign-in flow from scratch and use only the browser tab/window it opens","Discard stale callback URLs from previous attempts","Check for another login in progress that replaced the pending state, and cancel it before retrying"],"exampleFix":"// before: callback URL reused from an earlier login (state stale)\naccept_callback(current_state, parse_callback(\"http://localhost/callback?code=x&state=OLD\"))?\n// after: use the fresh callback from the current login\naccept_callback(current_state, parse_callback(fresh_callback_url))?","handlingStrategy":"try-catch","validationCode":"fn callback_matches_pending(expected: &str, url: &Url) -> bool {\n    url.query_pairs().any(|(k, v)| k == \"state\" && v == expected)\n}","typeGuard":null,"tryCatchPattern":"match accept_callback(expected_state, outcome) {\n    Ok(code) => code,\n    Err(e) if e.to_string().contains(\"state did not match\") => {\n        // stale or foreign callback: restart the login flow cleanly\n        cancel_pending_login();\n        start_new_login()?  // fresh state, fresh browser tab\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Complete each sign-in in the tab the flow opened; discard old tabs","Never run two concurrent logins against the same pending state","Do not hand-modify or re-encode callback URLs","If replaying callback URLs in tests, regenerate state each time"],"tags":["oauth","csrf","state-mismatch","security"],"backgroundTag":"oauth-state-mismatch","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}