unslothai/unsloth · error · CodexAuthError

Authorization flow was not found or expired.

Error message

Authorization flow was not found or expired.

What it means

Raised as CodexAuthError by get_flow on the first branch: the in-memory flow exists and matches the provider, but it is a detached/persisted flow (no live server, task, or persist callback) and reloading it from the persistence layer (_load_persisted_oauth_flow) returns None. The stale in-memory entry is then popped. In practice: the flow you are asking for no longer exists on disk.

Source

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

        "expires_at": int(flow.expires_at),
        "authorization_url": flow.authorization_url or None,
        "verification_url": flow.verification_url or None,
        "user_code": flow.user_code or None,
        "message": flow.message or None,
    }


def get_flow(provider_id: str, flow_id: str) -> OAuthFlow:
    flow = _flows.get(flow_id)
    if (
        flow is not None
        and flow.provider_id == provider_id
        and (flow.server is None and flow.task is None and flow.persist_bundle is None)
    ):
        persisted = _load_persisted_oauth_flow(provider_id, flow_id)
        if persisted is None:
            _flows.pop(flow_id, None)
            raise CodexAuthError("Authorization flow was not found or expired.")
        flow = persisted
        _flows[flow.id] = flow
    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:

View on GitHub (pinned to 203007d190)

Solutions

  1. Start a new authorization flow and use its fresh flow_id.
  2. Invalidate stored flow_ids in the UI whenever the backend restarts or the provider is disconnected.
  3. If multi-worker, ensure all workers share the same installation DB so persisted flows are visible.
  4. Check whether a cleanup task or TTL sweeper deleted the record before the client polled.

Example fix

// before
flow = codex_auth.get_flow(provider_id, flow_id_from_yesterday)

// after
try:
    flow = codex_auth.get_flow(provider_id, flow_id_from_yesterday)
except codex_auth.CodexAuthError:
    flow = await codex_auth.start_browser_flow(provider_id, persist_bundle)  # mint a new flow
Defensive patterns

Strategy: validation

Validate before calling

flow = None
try:
    flow = codex_auth.get_flow(provider_id, flow_id)
except codex_auth.CodexAuthError:
    flow = None
if flow is None:
    flow = await start_new_flow(provider_id)  # never reuse an unresolvable flow_id

Type guard

def flow_exists(provider_id: str, flow_id: str) -> bool:
    try:
        codex_auth.get_flow(provider_id, flow_id)
        return True
    except codex_auth.CodexAuthError:
        return False

Try / catch

try:
    flow = codex_auth.get_flow(provider_id, flow_id)
except codex_auth.CodexAuthError as exc:
    if "not found or expired" in str(exc):
        return RedirectResponse("/connect/chatgpt")  # restart UX
    raise

Prevention

When it happens

Trigger: Calling get_flow/complete_browser_flow with a flow_id from a previous Studio session after the persisted flow record was deleted, expired and cleaned up, or never committed; multiple workers where one deleted the DB row; the flows table was reset.

Common situations: Frontend holds a stale flow_id after a backend restart or DB clear; the user disconnected the provider which removed persisted flows; a different worker processed and deleted the flow first.

Related errors


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