{"record":{"id":"5cb8de469b4da408","repo":"danny-avila/LibreChat","slug":"oauth-flow-not-found","errorCode":null,"errorMessage":"OAuth flow not found","messagePattern":"OAuth flow not found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"api/server/routes/actions.js","lineNumber":99,"sourceCode":"\n    identifier = `${decodedState.user}:${action_id}`;\n\n    if (\n      !validateOAuthCsrf(req, res, identifier, OAUTH_CSRF_COOKIE_PATH) &&\n      !validateOAuthSession(req, decodedState.user)\n    ) {\n      logger.error('[Action OAuth] CSRF validation failed: no valid CSRF or session cookie', {\n        identifier,\n        hasCsrfCookie: !!req.cookies?.[OAUTH_CSRF_COOKIE],\n        hasSessionCookie: !!req.cookies?.[OAUTH_SESSION_COOKIE],\n      });\n      await flowManager.failFlow(identifier, 'oauth', 'CSRF validation failed');\n      return res.redirect(`${basePath}/oauth/error?error=csrf_validation_failed`);\n    }\n\n    const flowState = await flowManager.getFlowState(identifier, 'oauth');\n    if (!flowState) {\n      throw new Error('OAuth flow not found');\n    }\n\n    const tokenData = await getAccessToken(\n      {\n        code,\n        userId: decodedState.user,\n        identifier,\n        client_url: flowState.metadata.client_url,\n        redirect_uri: flowState.metadata.redirect_uri,\n        token_exchange_method: flowState.metadata.token_exchange_method,\n        allowedAddresses: flowState.metadata.allowedAddresses,\n        /** Encrypted values */\n        encrypted_oauth_client_id: flowState.metadata.encrypted_oauth_client_id,\n        encrypted_oauth_client_secret: flowState.metadata.encrypted_oauth_client_secret,\n      },\n      {\n        findToken,\n        updateToken,","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/api/server/routes/actions.js#L81-L117","documentation":"Thrown in the Action OAuth callback after CSRF validation succeeds but flowManager.getFlowState returns nothing. The transient OAuth flow record (created at the start of the flow) is no longer present, so the authorization code cannot be exchanged safely.","triggerScenarios":"The OAuth flow state expired or was evicted from its store before the callback returned; a duplicate/old callback hits the endpoint after the flow was completed or failed; the flow was never created (callback reached without a matching identifier).","commonSituations":"User takes a long time on the OAuth consent screen and the flow TTL elapses; replayed callback URL (refresh/bookmark); flow store restart or flush (e.g. in-memory store lost on redeploy); clock skew between issuer and callback.","solutions":["Re-initiate the OAuth flow from the start so a fresh flow state is created.","Increase the OAuth flow TTL if users consistently run past it on slow consent screens.","Use a durable flow store (DB/redis) instead of in-memory so flows survive restarts.","Detect duplicate callbacks and short-circuit before this code path."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const flowState = await flowManager.getFlowState(identifier, 'oauth');\nif (!flowState) {\n  return res.redirect(`${basePath}/oauth/error?error=flow_not_found`);\n}","typeGuard":null,"tryCatchPattern":"try { /* callback handler */ }\ncatch (e) {\n  if (/OAuth flow not found/.test(e.message)) {\n    return res.redirect(`${basePath}/oauth/error?error=expired_flow`);\n  }\n  throw e;\n}","preventionTips":["Use a persistent flow store (redis/db) rather than in-memory.","Set a flow TTL longer than the slowest realistic consent.","Idempotently handle replayed callback URLs."],"tags":["oauth","actions","state-management","auth-flow"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}