{"record":{"id":"eae1016dc1a26b4e","repo":"BloopAI/vibe-kanban","slug":"session-expired-please-log-in-again","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/relayBackendApi.ts","lineNumber":161,"sourceCode":"\n  const response = await fetch(`${baseUrl}${path}`, {\n    ...options,\n    headers,\n    credentials: 'include',\n  });\n\n  if (response.status === 401 && retryOn401) {\n    const newToken = await authRuntime.triggerRefresh();\n    if (newToken) {\n      headers.set('Authorization', `Bearer ${newToken}`);\n      return fetch(`${baseUrl}${path}`, {\n        ...options,\n        headers,\n        credentials: 'include',\n      });\n    }\n\n    throw new Error('Session expired. Please log in again.');\n  }\n\n  return response;\n}\n\nasync function parseErrorResponse(\n  response: Response,\n  fallbackMessage: string\n): Promise<Error> {\n  try {\n    const body = await response.json();\n    const message = body.error || body.message || fallbackMessage;\n    return new Error(`${message} (${response.status} ${response.statusText})`);\n  } catch {\n    return new Error(\n      `${fallbackMessage} (${response.status} ${response.statusText})`\n    );\n  }","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/web-core/src/shared/lib/relayBackendApi.ts#L143-L179","documentation":"makeAuthenticatedRequest in relayBackendApi.ts throws this when a relay/API call returns HTTP 401 and an attempted token refresh via authRuntime.triggerRefresh() fails to yield a new token. It signals the browser session (access + refresh tokens) is no longer usable and the user must re-authenticate interactively.","triggerScenarios":"Any authenticated relay call (e.g. createRemoteSession or makeAuthenticatedRelaySessionRequest for SPAKE2 enrollment/finish/signing refresh) receives a 401, and authRuntime.triggerRefresh() returns null/undefined — refresh token missing, expired, or rejected by the server.","commonSituations":"User leaves the app open past refresh-token lifetime; refresh cookie cleared or blocked (third-party cookie settings) so credentials:'include' sends nothing; server rotated/revoked the refresh token (logged in elsewhere); clock skew; relay backend restarted with in-memory sessions.","solutions":["Redirect the user to the login flow on catching this error (message instructs re-login).","Inspect authRuntime.triggerRefresh() — ensure the refresh endpoint and refresh cookie are correctly configured and the server accepts it.","Check that the relay/remote backend accepts the Authorization bearer token version (X-Client-Version mismatch can cause 401s).","Clear stale auth state (tokens/cookies) before re-login to avoid refresh loops."],"exampleFix":"// before\ntry {\n  await createRemoteSession(hostId);\n} catch (e) { console.error(e); }\n// after\ntry {\n  await createRemoteSession(hostId);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('Session expired')) {\n    authRuntime.logout();\n    window.location.assign('/login?reason=session-expired');\n  }\n}","handlingStrategy":"try-catch","validationCode":"const authRuntime = getAuthRuntime();\nconst token = await authRuntime.getToken();\nif (!token) redirectToLogin(); // no point calling if not even a token exists","typeGuard":"function isSessionExpiredError(e: unknown): e is Error {\n  return e instanceof Error && e.message.includes('Session expired');\n}","tryCatchPattern":"try {\n  await createRemoteSession(hostId);\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":["Gate API calls behind an auth-ready flag from the auth runtime","Proactively refresh the token before expiry (scheduled refresh) instead of waiting for 401","Ensure refresh cookies are sent (SameSite=None; Secure; credentials:'include')","Redirect to login immediately on this error to avoid retry loops"],"tags":["auth","session-expired","http-401"],"backgroundTag":"jwt-token-expired","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}