{"record":{"id":"fe795844ca4e63d7","repo":"BloopAI/vibe-kanban","slug":"session-expired-please-log-in-again-fe7958","errorCode":null,"errorMessage":"Session expired. Please log in again.","messagePattern":"Session expired\\. Please log in again\\.","errorType":"exception","errorClass":null,"httpStatus":401,"severity":"error","filePath":"packages/web-core/src/shared/lib/remoteApi.ts","lineNumber":94,"sourceCode":"    ...options,\n    headers,\n    credentials: 'include',\n  });\n\n  // Handle 401 - token may have expired\n  if (response.status === 401 && retryOn401) {\n    const newToken = await authRuntime.triggerRefresh();\n    if (newToken) {\n      // Retry the request with the new token\n      headers.set('Authorization', `Bearer ${newToken}`);\n      return fetch(`${baseUrl}${path}`, {\n        ...options,\n        headers,\n        credentials: 'include',\n      });\n    }\n    // Refresh failed, throw an auth error\n    throw new Error('Session expired. Please log in again.');\n  }\n\n  return response;\n}\n\nexport interface BulkUpdateIssueItem {\n  id: string;\n  changes: Partial<UpdateIssueRequest>;\n}\n\nexport interface BulkUpdateProjectItem {\n  id: string;\n  changes: Partial<UpdateProjectRequest>;\n}\n\nexport async function bulkUpdateProjects(\n  updates: BulkUpdateProjectItem[]\n): Promise<void> {","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/web-core/src/shared/lib/remoteApi.ts#L76-L112","documentation":"makeAuthenticatedRequest in remoteApi.ts throws this when a request returns HTTP 401 and the token refresh (authRuntime.triggerRefresh()) fails to produce a new token, so the request is not retried. It means the user's session cannot be silently restored.","triggerScenarios":"Any makeRequest-based remote API call gets 401; triggerRefresh() returns null because the refresh token is expired/absent or the refresh endpoint rejects it. Second 401 after refresh does NOT rethrow this (retryOn401 default true only retries once; a failed retry response is returned as-is).","commonSituations":"Long-lived tab whose refresh token expired; refresh cookie missing due to cookie policy or cross-site fetch; the shared API base URL changed (setRemoteApiBase) so the refresh endpoint differs; server-side session revocation.","solutions":["Catch the error and route the user to login (the message is user-facing).","Verify the auth runtime's refresh endpoint URL and that credentials:'include' cookies reach it (SameSite/cORS settings).","Check server logs for the refresh rejection reason (expired vs revoked vs invalid client version).","Clear local auth state before redirecting so the fresh login doesn't collide with stale tokens."],"exampleFix":"// before\nconst resp = await makeRequest('/v1/projects');\n// after\ntry {\n  const resp = await makeRequest('/v1/projects');\n} catch (e) {\n  if (e instanceof Error && e.message.includes('Session expired')) {\n    await authRuntime.logout();\n    window.location.assign('/login');\n  } else {\n    throw e;\n  }\n}","handlingStrategy":"try-catch","validationCode":"const authRuntime = getAuthRuntime();\nif (!(await authRuntime.getToken())) redirectToLogin();","typeGuard":"function isSessionExpiredError(e: unknown): e is Error {\n  return e instanceof Error && e.message.includes('Session expired');\n}","tryCatchPattern":"try {\n  const resp = await makeRequest('/v1/projects');\n} catch (e) {\n  if (isSessionExpiredError(e)) {\n    await authRuntime.logout();\n    window.location.assign('/login?reason=session-expired');\n  } else { throw e; }\n}","preventionTips":["Schedule proactive token refresh before the access token expires","Verify the refresh endpoint and its cookies work with credentials:'include' (SameSite/CORS)","Clear stale tokens before redirecting to a fresh login","Avoid retry loops: this error is terminal — always re-authenticate interactively"],"tags":["auth","session-expired","http-401","refresh-token"],"backgroundTag":"jwt-token-expired","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}