Hmbown/CodeWhale · error
OAuth error callback state did not match the pending login
Error message
OAuth error callback state did not match the pending login
What it means
When the OAuth provider redirects back with an error outcome, `accept_callback` still verifies the `state` parameter when the provider included one; if it does not match the pending login, this error is thrown before the error detail is surfaced. This ensures an attacker cannot fabricate error callbacks against an unrelated pending login, and distinguishes mismatched-state errors from genuine provider errors.
Solutions
- Restart the login flow and complete (or fail) it in the tab the flow opened
- Verify the provider preserves the state parameter verbatim on error redirects; URL-encoding differences must be decoded before comparison
- Discard the stale error callback and retry with a fresh state
Defensive patterns
Strategy: try-catch
Validate before calling
fn error_callback_state_ok(expected: &str, url: &Url) -> Option<bool> {
url.query_pairs().find(|(k, _)| k == "state")
.map(|(_, v)| v == expected) // None => provider omitted state, allowed
} Try / catch
match accept_callback(expected_state, outcome) {
Ok(_) => unreachable!("error outcome cannot succeed"),
Err(e) if e.to_string().contains("error callback state did not match") => {
// denial from a stale/different login: restart the flow
cancel_pending_login();
start_new_login()
}
Err(e) => return Err(e), // genuine provider error: read its detail
} Prevention
- Keep one pending login at a time; cancel the previous before starting a new one
- Verify the provider echoes state verbatim on error redirects (some re-encode it)
- Never trust an error callback whose state you cannot tie to your own request
When it happens
Trigger: `accept_callback` receives `CallbackOutcome::Error { error, description, state: Some(other_state) }` where `state` differs from `expected_state` — an error redirect carrying a state from a different login attempt.
Common situations: A user denies consent in a stale tab from a previous login while a newer one is pending; a provider that mangles/re-encodes the state on its error redirect; replaying an old denial URL.
Related errors
- OAuth callback state did not match the pending login
- Codewhale-owned OAuth path escaped the credentials directory
- Codewhale-owned OAuth path has an invalid basename
- Codewhale-owned xAI OAuth DACL is not current-user-only
- Codewhale-owned xAI OAuth DACL must grant only one user
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/a52228fdac82b323.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/oauth.rs:1233
Ok(CallbackOutcome::Success { code, state })
}
pub fn accept_callback(expected_state: &str, outcome: CallbackOutcome) -> Result<String> {
match outcome {
CallbackOutcome::Success { code, state } => {
anyhow::ensure!(
state == expected_state,
"OAuth callback state did not match the pending login"
);
Ok(code)
}
CallbackOutcome::Error {
error,
description,
state,
} => {
if let Some(state) = state {
anyhow::ensure!(
state == expected_state,
"OAuth error callback state did not match the pending login"
);
}
let detail = description
.filter(|text| !text.trim().is_empty())
.unwrap_or(error);
bail!("sign-in was not completed: {detail}")
}
}
}
fn parse_http_request_target(request_line: &str) -> Result<String> {
let mut parts = request_line.split_whitespace();
let method = parts.next().unwrap_or_default();
anyhow::ensure!(
method.eq_ignore_ascii_case("GET"),
"OAuth callback must be GET"View on GitHub (pinned to 73e0f67d83)