BloopAI/vibe-kanban · error

Session expired. Please log in again.

Error message

Session expired. Please log in again.

What it means

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.

Source

Thrown at packages/web-core/src/shared/lib/relayBackendApi.ts:161

  const response = await fetch(`${baseUrl}${path}`, {
    ...options,
    headers,
    credentials: 'include',
  });

  if (response.status === 401 && retryOn401) {
    const newToken = await authRuntime.triggerRefresh();
    if (newToken) {
      headers.set('Authorization', `Bearer ${newToken}`);
      return fetch(`${baseUrl}${path}`, {
        ...options,
        headers,
        credentials: 'include',
      });
    }

    throw new Error('Session expired. Please log in again.');
  }

  return response;
}

async function parseErrorResponse(
  response: Response,
  fallbackMessage: string
): Promise<Error> {
  try {
    const body = await response.json();
    const message = body.error || body.message || fallbackMessage;
    return new Error(`${message} (${response.status} ${response.statusText})`);
  } catch {
    return new Error(
      `${fallbackMessage} (${response.status} ${response.statusText})`
    );
  }

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Redirect the user to the login flow on catching this error (message instructs re-login).
  2. Inspect authRuntime.triggerRefresh() — ensure the refresh endpoint and refresh cookie are correctly configured and the server accepts it.
  3. Check that the relay/remote backend accepts the Authorization bearer token version (X-Client-Version mismatch can cause 401s).
  4. Clear stale auth state (tokens/cookies) before re-login to avoid refresh loops.

Example fix

// before
try {
  await createRemoteSession(hostId);
} catch (e) { console.error(e); }
// after
try {
  await createRemoteSession(hostId);
} catch (e) {
  if (e instanceof Error && e.message.includes('Session expired')) {
    authRuntime.logout();
    window.location.assign('/login?reason=session-expired');
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

const authRuntime = getAuthRuntime();
const token = await authRuntime.getToken();
if (!token) redirectToLogin(); // no point calling if not even a token exists

Type guard

function isSessionExpiredError(e: unknown): e is Error {
  return e instanceof Error && e.message.includes('Session expired');
}

Try / catch

try {
  await createRemoteSession(hostId);
} catch (e) {
  if (isSessionExpiredError(e)) {
    await authRuntime.logout();
    window.location.assign('/login?reason=session-expired');
  } else { throw e; }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/eae1016dc1a26b4e. Report an issue: GitHub.