BloopAI/vibe-kanban · error

Session expired. Please log in again.

Error message

Session expired. Please log in again.

What it means

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.

Source

Thrown at packages/web-core/src/shared/lib/remoteApi.ts:94

    ...options,
    headers,
    credentials: 'include',
  });

  // Handle 401 - token may have expired
  if (response.status === 401 && retryOn401) {
    const newToken = await authRuntime.triggerRefresh();
    if (newToken) {
      // Retry the request with the new token
      headers.set('Authorization', `Bearer ${newToken}`);
      return fetch(`${baseUrl}${path}`, {
        ...options,
        headers,
        credentials: 'include',
      });
    }
    // Refresh failed, throw an auth error
    throw new Error('Session expired. Please log in again.');
  }

  return response;
}

export interface BulkUpdateIssueItem {
  id: string;
  changes: Partial<UpdateIssueRequest>;
}

export interface BulkUpdateProjectItem {
  id: string;
  changes: Partial<UpdateProjectRequest>;
}

export async function bulkUpdateProjects(
  updates: BulkUpdateProjectItem[]
): Promise<void> {

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Catch the error and route the user to login (the message is user-facing).
  2. Verify the auth runtime's refresh endpoint URL and that credentials:'include' cookies reach it (SameSite/cORS settings).
  3. Check server logs for the refresh rejection reason (expired vs revoked vs invalid client version).
  4. Clear local auth state before redirecting so the fresh login doesn't collide with stale tokens.

Example fix

// before
const resp = await makeRequest('/v1/projects');
// after
try {
  const resp = await makeRequest('/v1/projects');
} catch (e) {
  if (e instanceof Error && e.message.includes('Session expired')) {
    await authRuntime.logout();
    window.location.assign('/login');
  } else {
    throw e;
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

const authRuntime = getAuthRuntime();
if (!(await authRuntime.getToken())) redirectToLogin();

Type guard

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

Try / catch

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

Prevention

When it happens

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

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

Related errors


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