{"record":{"id":"16cacd82241a0704","repo":"Kuberwastaken/claurst","slug":"missing-code-or-state-in-oauth-callback","errorCode":null,"errorMessage":"Missing code or state in OAuth callback","messagePattern":"Missing code or state in OAuth callback","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-rust/crates/cli/src/codex_oauth_flow.rs","lineNumber":178,"sourceCode":"        \"<html><body style='background:#131010;color:#f1ecec;display:flex;justify-content:center;align-items:center;height:100vh;font-family:system-ui'>\\\r\n         <div style='text-align:center'><h1 style='color:#fc533a'>Authorization Failed</h1><p>Check the terminal for details.</p></div></body></html>\"\r\n    };\r\n    let response = format!(\r\n        \"HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\nContent-Length: {}\\r\\nConnection: close\\r\\n\\r\\n{}\",\r\n        html.len(),\r\n        html\r\n    );\r\n    // Drop the BufReader so we can write back on the socket\r\n    drop(reader);\r\n    let _ = socket.write_all(response.as_bytes()).await;\r\n    let _ = socket.shutdown().await;\r\n\r\n    if !error.is_empty() {\r\n        bail!(\"OAuth error: {}\", error);\r\n    }\r\n\r\n    if code.is_empty() || state.is_empty() {\r\n        bail!(\"Missing code or state in OAuth callback\");\r\n    }\r\n\r\n    Ok((code, state))\r\n}\r\n\r\n/// Exchange authorization code for access tokens.\r\nasync fn exchange_code_for_tokens(code: &str, verifier: &str) -> anyhow::Result<CodexTokens> {\r\n    let client = reqwest::Client::new();\r\n    let params = [\r\n        (\"client_id\", CODEX_CLIENT_ID),\r\n        (\"code\", code),\r\n        (\"code_verifier\", verifier),\r\n        (\"grant_type\", \"authorization_code\"),\r\n        (\"redirect_uri\", CODEX_REDIRECT_URI),\r\n    ];\r\n\r\n    let resp = client\r\n        .post(CODEX_TOKEN_URL)\r","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/Kuberwastaken/claurst/blob/b0637c97ec34144387cbf2f74f65df6d16a6cef1/src-rust/crates/cli/src/codex_oauth_flow.rs#L160-L196","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: pasting/truncating the callback URL in the browser\nhttp://localhost:1455/auth/callback?\n\n// after: let the flow redirect the browser automatically, or paste the full URL\nhttp://localhost:1455/auth/callback?code=abc123&state=xyz789","handlingStrategy":"validation","validationCode":"// Before starting the flow, confirm the callback URL template requires code & state,\n// and validate any manually supplied callback URL before serving/processing it:\nfn validate_callback_url(url: &str) -> anyhow::Result<()> {\n    let q = url.split('?').nth(1).unwrap_or(\"\");\n    let has = |k: &str| q.split('&').any(|p| p.starts_with(&format!(\"{}=\", k)) && p.len() > k.len() + 1);\n    anyhow::ensure!(has(\"code\") && has(\"state\"), \"callback URL lacks code/state\");\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["oauth","callback","missing-parameter","cli"],"backgroundTag":"missing-required-argument","analyzedSha":"b0637c97ec34144387cbf2f74f65df6d16a6cef1","analyzedAt":"2026-09-10T00:24:58.650Z","contentChangedAt":"2026-09-10T00:24:58.650Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}