unslothai/unsloth · warning · CodexAuthError

The callback URL did not contain an authorization code.

Error message

The callback URL did not contain an authorization code.

What it means

Raised as CodexAuthError by complete_browser_flow as the final check before token exchange: the callback URL is structurally valid and the state matches, but the 'code' query parameter is absent or empty. OpenAI always appends code on a successful consent; an absent code usually means the redirect carried an error (e.g. error=access_denied) or the URL was truncated right where the code begins.

Source

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

    flow = get_flow(provider_id, flow_id)
    if flow.method != "browser" or flow.status != "pending" or flow.consumed:
        raise CodexAuthError("Authorization flow is no longer active.")
    parsed = urlparse(callback_url)
    expected = urlparse(flow.redirect_uri)
    if (
        parsed.scheme != expected.scheme
        or parsed.hostname != expected.hostname
        or parsed.port != expected.port
        or parsed.path != expected.path
        or parsed.fragment
    ):
        raise CodexAuthError("Paste the complete localhost ChatGPT callback URL.")
    query = parse_qs(parsed.query)
    if not secrets.compare_digest(query.get("state", [""])[0], flow.state):
        raise CodexAuthError("Authorization state did not match.")
    code = query.get("code", [""])[0]
    if not code:
        raise CodexAuthError("The callback URL did not contain an authorization code.")
    await _exchange_code(flow, code)
    return flow


async def cancel_flow(flow_id: str) -> None:
    flow = _flows.pop(flow_id, None)
    if not flow:
        return
    flow.status = "cancelled"
    if flow.task:
        flow.task.cancel()
    if flow.cleanup_task and flow.cleanup_task is not asyncio.current_task():
        flow.cleanup_task.cancel()
    if flow.server:
        flow.server.close()
        await flow.server.wait_closed()

View on GitHub (pinned to 203007d190)

Solutions

  1. If consent was denied, restart the flow and approve the ChatGPT permission prompt.
  2. Re-copy the complete callback URL — the code parameter is long and often the truncated part.
  3. Paste into a plain text editor first to verify the full query string survived.
  4. Check the URL for an error= parameter; its value tells you why OpenAI omitted the code.

Example fix

// before
await codex_auth.complete_browser_flow(provider_id, flow_id, url_without_code)

// after
from urllib.parse import urlparse, parse_qs
q = parse_qs(urlparse(callback_url).query)
if not q.get("code"):
    raise ValueError(f"callback missing code; error={q.get('error')}")
await codex_auth.complete_browser_flow(provider_id, flow_id, callback_url)
Defensive patterns

Strategy: validation

Validate before calling

q = parse_qs(urlparse(callback_url).query)
if not q.get("code", [""])[0]:
    err = q.get("error", ["unknown"])[0]
    reject(f"no authorization code (error={err}); user likely denied consent")

Type guard

def callback_has_code(callback_url: str) -> bool:
    from urllib.parse import urlparse, parse_qs
    return bool(parse_qs(urlparse(callback_url).query).get("code", [""])[0])

Try / catch

try:
    flow = await codex_auth.complete_browser_flow(provider_id, flow_id, pasted)
except codex_auth.CodexAuthError as exc:
    if "did not contain an authorization code" in str(exc):
        ask_user_to_recopy_or_reapprove()  # truncated paste or denied consent
    else:
        raise

Prevention

When it happens

Trigger: User denied the ChatGPT consent so the redirect contains error=access_denied with no code; the pasted URL was cut off before &code=...; query parameters reordered/dropped by a URL handler; consent timed out server-side redirecting with an error.

Common situations: Consent denial is the most common case; over-eager copy tools truncating long URLs at line breaks; messaging apps splitting the URL; the user copying while the page was mid-redirect.

Related errors


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