Kuberwastaken/claurst · error · anyhow::Error
Missing code or state in OAuth callback
Error message
Missing code or state in OAuth callback
What it means
During the Codex OAuth login flow, claurst runs a temporary local HTTP server to receive the provider's redirect. When the browser hits the callback URL, wait_for_callback parses the query string and requires both `code` and `state` parameters. If either is absent or empty after URL-decoding, the flow is aborted because an authorization code is required to exchange for tokens and the state value is required to validate the redirect against the original request.
Solutions
- Restart the login flow (`claurst auth login` / run_oauth_flow_with_label) and complete the sign-in in the browser window that opens, without editing the URL
- Check that the provider's redirect actually reached the local server: verify the full callback URL in the browser address bar contains both `code=` and `state=` before it is served
- Clear provider session/cookies and retry — some providers redirect without parameters when the SSO session is stale
- If running behind a proxy or firewall, ensure the localhost callback port is reachable and the query string is not being rewritten or stripped
- If the provider consistently omits `state`, verify the authorization URL built by run_oauth_flow_with_label includes state and that the provider config preserves query parameters on redirect
Example fix
// before: pasting/truncating the callback URL in the browser http://localhost:1455/auth/callback? // after: let the flow redirect the browser automatically, or paste the full URL http://localhost:1455/auth/callback?code=abc123&state=xyz789
Defensive patterns
Strategy: validation
Validate before calling
// Before starting the flow, confirm the callback URL template requires code & state,
// and validate any manually supplied callback URL before serving/processing it:
fn validate_callback_url(url: &str) -> anyhow::Result<()> {
let q = url.split('?').nth(1).unwrap_or("");
let has = |k: &str| q.split('&').any(|p| p.starts_with(&format!("{}=", k)) && p.len() > k.len() + 1);
anyhow::ensure!(has("code") && has("state"), "callback URL lacks code/state");
Ok(())
} Prevention
- Never type the localhost callback URL by hand; always follow the browser redirect from the flow
- Complete the sign-in promptly — stale provider sessions can redirect without parameters
- Keep browser extensions/privacy tools from stripping query parameters on localhost
- Check the terminal: the earlier "OAuth error" bail is the sibling case and names the provider's reason
When it happens
Trigger: The local callback server receives an HTTP request whose query string contains no `code` parameter or no `state` parameter (either missing entirely or URL-decoding to an empty string), and the query also lacks an `error`/`error_description` parameter (that case bails earlier with "OAuth error"). E.g. the user pastes the bare redirect URL `http://localhost:PORT/callback?` or a URL with only `state` into the browser.
Common situations: The user manually navigates to or bookmarks the localhost callback URL; the auth server redirects with only some parameters; the user copies the callback URL but truncates the query string; a proxy or browser extension strips query parameters; the OAuth provider redirects to the base redirect_uri without parameters after a session timeout.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- No query string in callback
- OAuth state mismatch — possible CSRF attack
- OAuth callback path mismatch: expected
- Failed to bind port
- OAuth callback timeout (5 minutes)
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/16cacd82241a0704.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/cli/src/codex_oauth_flow.rs:178
"<html><body style='background:#131010;color:#f1ecec;display:flex;justify-content:center;align-items:center;height:100vh;font-family:system-ui'>\
<div style='text-align:center'><h1 style='color:#fc533a'>Authorization Failed</h1><p>Check the terminal for details.</p></div></body></html>"
};
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
html.len(),
html
);
// Drop the BufReader so we can write back on the socket
drop(reader);
let _ = socket.write_all(response.as_bytes()).await;
let _ = socket.shutdown().await;
if !error.is_empty() {
bail!("OAuth error: {}", error);
}
if code.is_empty() || state.is_empty() {
bail!("Missing code or state in OAuth callback");
}
Ok((code, state))
}
/// Exchange authorization code for access tokens.
async fn exchange_code_for_tokens(code: &str, verifier: &str) -> anyhow::Result<CodexTokens> {
let client = reqwest::Client::new();
let params = [
("client_id", CODEX_CLIENT_ID),
("code", code),
("code_verifier", verifier),
("grant_type", "authorization_code"),
("redirect_uri", CODEX_REDIRECT_URI),
];
let resp = client
.post(CODEX_TOKEN_URL)
View on GitHub (pinned to b0637c97ec)