unslothai/unsloth · warning · CodexAuthError

Authorization state did not match.

Error message

Authorization state did not match.

What it means

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.

Source

Thrown at studio/backend/core/inference/openai_codex_auth.py:334

        flow.message = "ChatGPT authorization failed. Please reconnect."
        await _persist_terminal_flow(flow)
        raise
    flow.status = "connected"
    if flow.server:
        flow.server.close()
        flow.server = None


async def _loopback_handler(
    flow: OAuthFlow, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
) -> None:
    try:
        first = await asyncio.wait_for(reader.readline(), timeout = 5)
        target = first.decode("ascii", "ignore").split(" ")[1]
        parsed = urlparse(target)
        query = parse_qs(parsed.query)
        if parsed.path != OPENAI_CODEX_CALLBACK_PATH or query.get("state", [""])[0] != flow.state:
            raise CodexAuthError("Authorization state did not match.")
        code = query.get("code", [""])[0]
        if not code or flow.consumed:
            raise CodexAuthError("Authorization callback was invalid or already used.")
        await _exchange_code(flow, code)
        message = "ChatGPT connected. You can close this window."
    except Exception as exc:
        # Stray requests and state mismatches must not poison the active flow.
        if flow.consumed and flow.status == "pending":
            flow.status = "error"
            flow.message = str(exc) if isinstance(exc, CodexAuthError) else "Authorization failed."
        message = "Authorization failed. Return to Unsloth Studio and try again."
    body = (
        "<!doctype html><meta charset=utf-8><title>Unsloth Studio</title>"
        + "<p>"
        + message.replace("&", "&amp;").replace("<", "&lt;")
        + "</p>"
    ).encode()
    writer.write(

View on GitHub (pinned to 203007d190)

Solutions

  1. Return to Studio and complete the flow from its current authorization URL — a mismatched stray request does not abort the pending flow.
  2. Ensure a fresh authorization URL is used after restarting a flow (do not rely on cached/old tabs).
  3. If hand-copying URLs, copy the complete URL including the state and code query parameters unmodified.
  4. Verify only one flow's loopback server is bound to the port at a time.

Example fix

// before
# user pastes an old redirect: http://127.0.0.1:PORT/callback?state=OLD&code=OLD
await complete_browser_flow(provider_id, flow_id, old_url)

// after
# always copy the callback URL produced by the CURRENT authorization attempt
flow = codex_auth.start_browser_flow(provider_id, persist_bundle)
await complete_browser_flow(provider_id, flow.id, current_callback_url)
Defensive patterns

Strategy: try-catch

Validate before calling

from urllib.parse import urlparse, parse_qs

p = urlparse(callback_url)
q = parse_qs(p.query)
if q.get("state", [""])[0] != flow.state or p.path != codex_auth.OPENAI_CODEX_CALLBACK_PATH:
    reject("stale or foreign callback")

Type guard

def callback_state_matches(callback_url: str, flow: codex_auth.OAuthFlow) -> bool:
    import secrets
    from urllib.parse import urlparse, parse_qs
    q = parse_qs(urlparse(callback_url).query)
    return secrets.compare_digest(q.get("state", [""])[0], flow.state)

Try / catch

try:
    handle_callback(flow, callback_url)
except codex_auth.CodexAuthError as exc:
    if "state did not match" in str(exc):
        ignore()  # stray request; pending flow is deliberately NOT poisoned
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/1201ec51cdc350ed. Report an issue: GitHub.