unslothai/unsloth · error · CodexAuthError

Authorization flow was cancelled before credentials were sav

Error message

Authorization flow was cancelled before credentials were saved.

What it means

Raised as CodexAuthError inside _exchange_code after a successful token exchange when flow.persist_bundle is None or flow.status is no longer 'pending'. The tokens were obtained from OpenAI but there is no way to save them because the flow was cancelled (or never wired with a persistence callback) at that moment. The except block then marks the flow as errored and persists the terminal state before re-raising.

Source

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

    verifier: str | None = None,
    redirect_uri: str | None = None,
) -> None:
    if flow.consumed:
        raise CodexAuthError("Authorization callback was already used.")
    flow.consumed = True
    try:
        body = await _token_request(
            {
                "grant_type": "authorization_code",
                "client_id": OPENAI_CODEX_CLIENT_ID,
                "code": code,
                "redirect_uri": redirect_uri or flow.redirect_uri,
                "code_verifier": verifier or flow.verifier,
            }
        )
        bundle = _validate_token_payload(body)
        if flow.persist_bundle is None or flow.status != "pending":
            raise CodexAuthError("Authorization flow was cancelled before credentials were saved.")
        persisted = flow.persist_bundle(flow.provider_id, bundle)
        if persisted is not None:
            await persisted
    except Exception:
        flow.status = "error"
        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:

View on GitHub (pinned to 203007d190)

Solutions

  1. Simply start a new connection flow — the obtained credentials are intentionally discarded, nothing was saved.
  2. If building custom flows, always pass persist_bundle when constructing the flow so a successful exchange can be saved.
  3. Avoid cancelling flows while the user's browser is still on the OpenAI consent page; wait for the flow TTL to expire instead.
  4. Check flow.status == 'pending' before issuing cancel to reduce the race window.

Example fix

// before
flow = OAuthFlow(id=..., provider_id=..., method="browser", ...)  # no persist_bundle

// after
flow = OAuthFlow(
    id=...,
    provider_id=...,
    method="browser",
    persist_bundle=persist_oauth_bundle,  # successful exchange can now be saved
    ...,
)
Defensive patterns

Strategy: try-catch

Validate before calling

flow = codex_auth.get_flow(provider_id, flow_id)
assert flow.status == "pending", f"flow is {flow.status}; completing now risks a cancel race"
assert flow.persist_bundle is not None, "flow lacks persistence; exchange cannot be saved"

Type guard

def flow_is_persistable(flow: codex_auth.OAuthFlow) -> bool:
    return flow.persist_bundle is not None and flow.status == "pending"

Try / catch

try:
    flow = await complete(flow_id, callback_url)
except codex_auth.CodexAuthError as exc:
    if "cancelled" in str(exc):
        flow = await start_new_flow(provider_id)  # tokens were discarded; restart
    else:
        raise

Prevention

When it happens

Trigger: User clicks 'Cancel' in Studio while the browser is completing the OAuth redirect — cancel_flow sets status='cancelled' and the race is lost when _exchange_code reaches the persist step; a flow object constructed without a persist_bundle callable; the flow expired and get_flow set status='error' just before the callback arrived.

Common situations: Race between cancellation and the authorization redirect landing; tests or custom integrations building OAuthFlow objects directly without the persistence callback; a long user delay on the ChatGPT consent page until after the flow was cancelled.

Related errors


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