unslothai/unsloth · error · CodexAuthError

Authorization callback was already used.

Error message

Authorization callback was already used.

What it means

Raised as CodexAuthError from _exchange_code when the OAuthFlow object already has consumed=True at entry. A flow is single-use: the first call to _exchange_code sets flow.consumed = True immediately, so any second attempt to exchange a code against the same flow (double-click, duplicate callback, retry after a partial failure) fails with this message before any network call is made.

Source

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

            raise CodexReauthorizationRequired(
                "ChatGPT authorization is no longer valid. Please reconnect."
            )
        raise CodexAuthError("ChatGPT authorization failed. Please reconnect.")
    try:
        return response.json()
    except Exception as exc:
        raise CodexAuthError("ChatGPT returned an invalid authorization response.") from exc


async def _exchange_code(
    flow: OAuthFlow,
    code: str,
    *,
    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:

View on GitHub (pinned to 203007d190)

Solutions

  1. Treat this error as benign in the success path — if the first exchange succeeded, the flow is connected; poll flow.status instead of re-completing.
  2. Fix the caller to submit the callback exactly once (disable the button after first click, dedupe on flow_id).
  3. Start a new flow if the first exchange genuinely failed; a consumed flow can never be reused.
  4. Check get_flow() status before calling complete_browser_flow and skip if status is already 'connected'.

Example fix

// before
@app.post("/complete")
async def complete(flow_id: str, callback_url: str):
    return await codex_auth.complete_browser_flow(provider_id, flow_id, callback_url)  # double-submit reuses flow

// after
@app.post("/complete")
async def complete(flow_id: str, callback_url: str):
    flow = codex_auth.get_flow(provider_id, flow_id)
    if flow.status == "connected":
        return flow  # idempotent: already exchanged
    return await codex_auth.complete_browser_flow(provider_id, flow_id, callback_url)
Defensive patterns

Strategy: validation

Validate before calling

flow = codex_auth.get_flow(provider_id, flow_id)
if flow.consumed:
    if flow.status == "connected":
        handle_already_connected(flow)  # benign
    else:
        start_new_flow()  # consumed and failed: cannot reuse

Type guard

def flow_is_reusable(flow: codex_auth.OAuthFlow) -> bool:
    return not flow.consumed and flow.status == "pending"

Try / catch

try:
    await complete(flow_id, callback_url)
except codex_auth.CodexAuthError as exc:
    if "already used" in str(exc):
        flow = codex_auth.get_flow(provider_id, flow_id)
        if flow.status == "connected":
            return flow  # idempotent success
        raise

Prevention

When it happens

Trigger: The loopback HTTP handler receives two callback requests with valid codes; complete_browser_flow is invoked twice for the same flow_id; a client retries the completion request after a timeout while the first attempt already consumed the flow; the browser fires both an automatic redirect and a manual paste of the callback URL.

Common situations: Frontend double-submits the 'complete connection' form; user pastes the callback URL after the loopback server already handled the redirect; an HTTP client auto-retries a POST on connection reuse; a stale flow object was rehydrated from persistence while an in-flight exchange had already run.

Related errors


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