{"record":{"id":"1201ec51cdc350ed","repo":"unslothai/unsloth","slug":"authorization-state-did-not-match","errorCode":null,"errorMessage":"Authorization state did not match.","messagePattern":"Authorization state did not match\\.","errorType":"exception","errorClass":"CodexAuthError","httpStatus":400,"severity":"warning","filePath":"studio/backend/core/inference/openai_codex_auth.py","lineNumber":334,"sourceCode":"        flow.message = \"ChatGPT authorization failed. Please reconnect.\"\n        await _persist_terminal_flow(flow)\n        raise\n    flow.status = \"connected\"\n    if flow.server:\n        flow.server.close()\n        flow.server = None\n\n\nasync def _loopback_handler(\n    flow: OAuthFlow, reader: asyncio.StreamReader, writer: asyncio.StreamWriter\n) -> None:\n    try:\n        first = await asyncio.wait_for(reader.readline(), timeout = 5)\n        target = first.decode(\"ascii\", \"ignore\").split(\" \")[1]\n        parsed = urlparse(target)\n        query = parse_qs(parsed.query)\n        if parsed.path != OPENAI_CODEX_CALLBACK_PATH or query.get(\"state\", [\"\"])[0] != flow.state:\n            raise CodexAuthError(\"Authorization state did not match.\")\n        code = query.get(\"code\", [\"\"])[0]\n        if not code or flow.consumed:\n            raise CodexAuthError(\"Authorization callback was invalid or already used.\")\n        await _exchange_code(flow, code)\n        message = \"ChatGPT connected. You can close this window.\"\n    except Exception as exc:\n        # Stray requests and state mismatches must not poison the active flow.\n        if flow.consumed and flow.status == \"pending\":\n            flow.status = \"error\"\n            flow.message = str(exc) if isinstance(exc, CodexAuthError) else \"Authorization failed.\"\n        message = \"Authorization failed. Return to Unsloth Studio and try again.\"\n    body = (\n        \"<!doctype html><meta charset=utf-8><title>Unsloth Studio</title>\"\n        + \"<p>\"\n        + message.replace(\"&\", \"&amp;\").replace(\"<\", \"&lt;\")\n        + \"</p>\"\n    ).encode()\n    writer.write(","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/inference/openai_codex_auth.py#L316-L352","documentation":"Raised as CodexAuthError by the loopback HTTP handler when the incoming callback request path is not OPENAI_CODEX_CALLBACK_PATH or the query 'state' parameter does not equal flow.state. The state check is the standard OAuth CSRF defense binding the callback to this exact flow. The except block is deliberately conservative: an unmatched state alone does NOT poison the flow unless it was consumed, so a stray request cannot kill a pending authorization.","triggerScenarios":"A browser tab with a stale authorization redirect from an earlier flow hits the loopback port while a new flow listens (state differs); another local application or scanner requests a random path on the loopback port; the state query parameter is dropped or URL-encoded differently than generated; two flows share the same port.","commonSituations":"User reuses an old browser tab holding the previous consent redirect; port reuse across sequential flows so an old callback lands on a new server; the callback URL was hand-edited; browser extensions stripping query parameters.","solutions":["Return to Studio and complete the flow from its current authorization URL — a mismatched stray request does not abort the pending flow.","Ensure a fresh authorization URL is used after restarting a flow (do not rely on cached/old tabs).","If hand-copying URLs, copy the complete URL including the state and code query parameters unmodified.","Verify only one flow's loopback server is bound to the port at a time."],"exampleFix":"// before\n# user pastes an old redirect: http://127.0.0.1:PORT/callback?state=OLD&code=OLD\nawait complete_browser_flow(provider_id, flow_id, old_url)\n\n// after\n# always copy the callback URL produced by the CURRENT authorization attempt\nflow = codex_auth.start_browser_flow(provider_id, persist_bundle)\nawait complete_browser_flow(provider_id, flow.id, current_callback_url)","handlingStrategy":"try-catch","validationCode":"from urllib.parse import urlparse, parse_qs\n\np = urlparse(callback_url)\nq = parse_qs(p.query)\nif q.get(\"state\", [\"\"])[0] != flow.state or p.path != codex_auth.OPENAI_CODEX_CALLBACK_PATH:\n    reject(\"stale or foreign callback\")","typeGuard":"def callback_state_matches(callback_url: str, flow: codex_auth.OAuthFlow) -> bool:\n    import secrets\n    from urllib.parse import urlparse, parse_qs\n    q = parse_qs(urlparse(callback_url).query)\n    return secrets.compare_digest(q.get(\"state\", [\"\"])[0], flow.state)","tryCatchPattern":"try:\n    handle_callback(flow, callback_url)\nexcept codex_auth.CodexAuthError as exc:\n    if \"state did not match\" in str(exc):\n        ignore()  # stray request; pending flow is deliberately NOT poisoned\n    else:\n        raise","preventionTips":["Always launch the authorization from the current flow's URL; close old tabs.","Never mix callbacks between concurrent flows.","Remember a state mismatch alone does not abort the pending flow — you can just retry with the correct URL.","Copy callback URLs wholesale; hand-editing breaks state."],"tags":["oauth","csrf","state-mismatch","loopback-server","codex"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}