unslothai/unsloth · error · CodexAuthError
Authorization flow is no longer active.
Error message
Authorization flow is no longer active.
What it means
Raised as CodexAuthError by complete_browser_flow as a precondition guard: the located flow must be a browser-method flow, still in status 'pending', and not yet consumed. Any deviation — cancelled flow, errored flow, expired flow, completed flow, or a device-method flow submitted to the browser completion API — raises immediately before the callback URL is parsed.
Source
Thrown at studio/backend/core/inference/openai_codex_auth.py:609
elif flow is None or flow.provider_id != provider_id:
flow = _load_persisted_oauth_flow(provider_id, flow_id)
if flow is None:
raise CodexAuthError("Authorization flow was not found or expired.")
_flows[flow.id] = flow
if time.time() >= flow.expires_at and flow.status == "pending":
flow.status = "error"
flow.message = "Authorization expired. Start a new connection."
if flow.task:
flow.task.cancel()
if flow.server:
flow.server.close()
return flow
async def complete_browser_flow(provider_id: str, flow_id: str, callback_url: str) -> OAuthFlow:
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 flowView on GitHub (pinned to 203007d190)
Solutions
- Poll get_flow().status first; only call complete_browser_flow while status == 'pending'.
- Make the completion call idempotent client-side (disable after first submit).
- If the flow was cancelled/expired, start a new flow — completion can never resume it.
- Route device flows to their own completion path, not the browser one.
Example fix
// before
await codex_auth.complete_browser_flow(provider_id, flow_id, callback_url) # retried blindly
// after
flow = codex_auth.get_flow(provider_id, flow_id)
if flow.method == "browser" and flow.status == "pending" and not flow.consumed:
flow = 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.method != "browser" or flow.status != "pending" or flow.consumed:
start_new_flow(provider_id) # completion is only legal on a pending, unconsumed browser flow Type guard
def flow_accepts_completion(flow: codex_auth.OAuthFlow) -> bool:
return flow.method == "browser" and flow.status == "pending" and not flow.consumed Try / catch
try:
flow = await codex_auth.complete_browser_flow(provider_id, flow_id, callback_url)
except codex_auth.CodexAuthError as exc:
if "no longer active" in str(exc):
flow = codex_auth.get_flow(provider_id, flow_id)
if flow.status == "connected":
return flow # already done
start_new_flow(provider_id)
else:
raise Prevention
- Precheck flow.status/method/consumed before calling completion.
- Submit completion exactly once per flow (idempotent UI).
- Route device flows to device-specific handling.
- Treat cancelled/expired flows as unrecoverable; always mint a new flow.
When it happens
Trigger: Calling complete_browser_flow twice (second call sees consumed=True or status 'connected'); submitting a device flow's flow_id to the browser endpoint; the flow was cancelled or expired (get_flow already flipped pending flows to 'error' on TTL); the exchange failed earlier and status became 'error'.
Common situations: Frontend retries a completion request after timeout; user clicks cancel then pastes the URL anyway; polling UX resubmits after the flow already finished; mixing up flow ids between device and browser connections.
Related errors
- Authorization callback was already used.
- Authorization flow was not found or expired.
- The callback URL did not contain an authorization code.
- Bad candidate spec fragment '{part}' (expected key=value)
- ChatGPT credential update is busy. Please retry.
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/642654ad058de070.
Report an issue: GitHub.