{"record":{"id":"fd6a9ce5ac8924af","repo":"QuantumNous/new-api","slug":"failed-to-sign-out-session-fd6a9c","errorCode":null,"errorMessage":"Failed to sign out session","messagePattern":"Failed to sign out session","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"web/src/features/profile/components/login-sessions-card.tsx","lineNumber":80,"sourceCode":"  const [revokeTarget, setRevokeTarget] = useState<LoginSession | null>(null)\n  const [confirmOthers, setConfirmOthers] = useState(false)\n\n  const sessionsQuery = useQuery({\n    queryKey: sessionQueryKey,\n    queryFn: async () => {\n      const response = await getLoginSessions()\n      if (!response.success) {\n        throw new Error(response.message || t('Failed to load login sessions'))\n      }\n      return response.data ?? []\n    },\n  })\n\n  const revokeMutation = useMutation({\n    mutationFn: async (sid: string) => {\n      const response = await revokeLoginSession(sid)\n      if (!response.success) {\n        throw new Error(response.message || t('Failed to sign out session'))\n      }\n      return sid\n    },\n    onSuccess: async (sid) => {\n      const revokedCurrent = sessionsQuery.data?.some(\n        (session) => session.sid === sid && session.current\n      )\n      setRevokeTarget(null)\n      if (revokedCurrent) {\n        clearAuthenticatedClientState(queryClient)\n        void navigate({ to: '/sign-in', replace: true })\n        return\n      }\n      toast.success(t('Session signed out'))\n      await queryClient.invalidateQueries({ queryKey: sessionQueryKey })\n    },\n    onError: (error: Error) => toast.error(error.message),\n  })","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/QuantumNous/new-api/blob/e2c7aa7b102c2075eae2377df3508658d45e88dc/web/src/features/profile/components/login-sessions-card.tsx#L62-L98","documentation":"Thrown by the revoke-session mutation in the profile Login Sessions card when the backend API call revokeLoginSession(sid) either rejects (network/HTTP failure) or resolves with success=false. The error text is only the fallback; when the server supplies response.message that message is used instead. It surfaces to the user via onError -> toast.error.","triggerScenarios":"POST to the session-revoke endpoint for a specific sid while the session was already revoked/expired server-side, the auth token is invalid or expired (401), the sid does not belong to the current user, or the API is unreachable (network error / proxy down).","commonSituations":"Session list is stale (session already revoked in another tab or by the server's session TTL), the user's token was refreshed mid-flight, logging out from a device whose session row was already purged, or a dev environment where the Go backend is not running.","solutions":["Open the browser network tab and inspect the revoke request's status code and body; the real cause is in response.message, not the fallback text.","If the session list is stale, refresh it (the panel already invalidates sessionQueryKey) and retry revoke against a sid that still exists.","If the auth token expired, sign in again; the API client should redirect on 401.","Verify the backend route for session revocation is enabled and the session store (e.g. Redis for multi-node) is reachable, since session state must be shared across instances."],"exampleFix":"// before\nconst response = await revokeLoginSession(sid)\nif (!response.success) {\n  throw new Error(response.message || t('Failed to sign out session'))\n}\n// after: treat 'already revoked' as success so stale lists don't error\nconst response = await revokeLoginSession(sid)\nif (!response.success && !/not found|already/i.test(response.message ?? '')) {\n  throw new Error(response.message || t('Failed to sign out session'))\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"onError: (error: Error) => {\n  const msg = error.message || t('Failed to sign out session')\n  if (/not found|already (revoked|signed out)/i.test(msg)) {\n    void queryClient.invalidateQueries({ queryKey: sessionQueryKey })\n    return\n  }\n  toast.error(msg)\n}","preventionTips":["Invalidate sessionQueryKey after any auth event so the sid list never goes stale before revoke.","Keep the API client's 401 interceptor active so expired tokens redirect instead of surfacing as generic failures.","Surface response.message in toasts (already done) and log sid correlation for support triage."],"tags":["auth","session-management","tanstack-query","api-error"],"backgroundTag":null,"analyzedSha":"e2c7aa7b102c2075eae2377df3508658d45e88dc","analyzedAt":"2026-08-15T10:35:18.111Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}