{"record":{"id":"642654ad058de070","repo":"unslothai/unsloth","slug":"authorization-flow-is-no-longer-active","errorCode":null,"errorMessage":"Authorization flow is no longer active.","messagePattern":"Authorization flow is no longer active\\.","errorType":"exception","errorClass":"CodexAuthError","httpStatus":400,"severity":"error","filePath":"studio/backend/core/inference/openai_codex_auth.py","lineNumber":609,"sourceCode":"    elif flow is None or flow.provider_id != provider_id:\n        flow = _load_persisted_oauth_flow(provider_id, flow_id)\n        if flow is None:\n            raise CodexAuthError(\"Authorization flow was not found or expired.\")\n        _flows[flow.id] = flow\n    if time.time() >= flow.expires_at and flow.status == \"pending\":\n        flow.status = \"error\"\n        flow.message = \"Authorization expired. Start a new connection.\"\n        if flow.task:\n            flow.task.cancel()\n        if flow.server:\n            flow.server.close()\n    return flow\n\n\nasync def complete_browser_flow(provider_id: str, flow_id: str, callback_url: str) -> OAuthFlow:\n    flow = get_flow(provider_id, flow_id)\n    if flow.method != \"browser\" or flow.status != \"pending\" or flow.consumed:\n        raise CodexAuthError(\"Authorization flow is no longer active.\")\n    parsed = urlparse(callback_url)\n    expected = urlparse(flow.redirect_uri)\n    if (\n        parsed.scheme != expected.scheme\n        or parsed.hostname != expected.hostname\n        or parsed.port != expected.port\n        or parsed.path != expected.path\n        or parsed.fragment\n    ):\n        raise CodexAuthError(\"Paste the complete localhost ChatGPT callback URL.\")\n    query = parse_qs(parsed.query)\n    if not secrets.compare_digest(query.get(\"state\", [\"\"])[0], flow.state):\n        raise CodexAuthError(\"Authorization state did not match.\")\n    code = query.get(\"code\", [\"\"])[0]\n    if not code:\n        raise CodexAuthError(\"The callback URL did not contain an authorization code.\")\n    await _exchange_code(flow, code)\n    return flow","sourceCodeStart":591,"sourceCodeEnd":627,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/inference/openai_codex_auth.py#L591-L627","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","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."],"exampleFix":"// before\nawait codex_auth.complete_browser_flow(provider_id, flow_id, callback_url)  # retried blindly\n\n// after\nflow = codex_auth.get_flow(provider_id, flow_id)\nif flow.method == \"browser\" and flow.status == \"pending\" and not flow.consumed:\n    flow = await codex_auth.complete_browser_flow(provider_id, flow_id, callback_url)","handlingStrategy":"validation","validationCode":"flow = codex_auth.get_flow(provider_id, flow_id)\nif flow.method != \"browser\" or flow.status != \"pending\" or flow.consumed:\n    start_new_flow(provider_id)  # completion is only legal on a pending, unconsumed browser flow","typeGuard":"def flow_accepts_completion(flow: codex_auth.OAuthFlow) -> bool:\n    return flow.method == \"browser\" and flow.status == \"pending\" and not flow.consumed","tryCatchPattern":"try:\n    flow = await codex_auth.complete_browser_flow(provider_id, flow_id, callback_url)\nexcept codex_auth.CodexAuthError as exc:\n    if \"no longer active\" in str(exc):\n        flow = codex_auth.get_flow(provider_id, flow_id)\n        if flow.status == \"connected\":\n            return flow  # already done\n        start_new_flow(provider_id)\n    else:\n        raise","preventionTips":["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."],"tags":["oauth","flow-state","state-machine","validation","codex"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}