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("&", "&").replace("<", "<")
+ "</p>"
).encode()
writer.write(View on GitHub (pinned to 203007d190)
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.
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
- 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.
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
- Authorization callback was invalid or already used.
- ChatGPT credential update is busy. Please retry.
- ChatGPT returned an invalid access token.
- The ChatGPT account identifier was missing.
- ChatGPT returned an invalid token response.
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/1201ec51cdc350ed.
Report an issue: GitHub.