koala73/worldmonitor · error

Authenticated account changed during push setup

Error message

Authenticated account changed during push setup

What it means

Thrown by assertExpectedAccount() in src/services/push-notifications.ts, which guards every step of authFetch and subscribeToPush. The flow captures an expectedUserId when it starts; if Clerk's current user (getCurrentClerkUser()?.id) no longer matches at any checkpoint, the operation aborts. This prevents registering a push subscription — which carries browser-level credentials — against the wrong user's row after a mid-flow account switch.

Source

Thrown at src/services/push-notifications.ts:99

  auth: string;
  userAgent: string;
}

function subscriptionToPayload(sub: PushSubscription): SubscriptionPayload | null {
  const p256dh = arrayBufferToBase64(sub.getKey('p256dh'));
  const auth = arrayBufferToBase64(sub.getKey('auth'));
  if (!p256dh || !auth || !sub.endpoint) return null;
  return {
    endpoint: sub.endpoint,
    p256dh,
    auth,
    userAgent: typeof navigator !== 'undefined' ? navigator.userAgent.slice(0, 200) : '',
  };
}

function assertExpectedAccount(expectedUserId?: string): void {
  if (expectedUserId && getCurrentClerkUser()?.id !== expectedUserId) {
    throw new Error('Authenticated account changed during push setup');
  }
}

async function authFetch(
  path: string,
  init: RequestInit,
  expectedUserId?: string,
): Promise<Response> {
  assertExpectedAccount(expectedUserId);
  const token = await getClerkToken();
  if (!token) throw new Error('Not authenticated');
  assertExpectedAccount(expectedUserId);
  return fetch(path, {
    ...init,
    headers: {
      'Content-Type': 'application/json',
      ...(init.headers ?? {}),
      Authorization: `Bearer ${token}`,

View on GitHub (pinned to eeab0a219f)

Solutions

  1. Discard the in-flight flow entirely and restart it with the new user's id — never resume a crossed flow.
  2. Pass expectedUserId from the component that owns the current session (re-read at flow start), not a value cached across renders.
  3. Subscribe to Clerk user-change events and cancel/abort pending push setup on change.
  4. On catch, re-check getCurrentClerkUser()?.id and re-anchor the UI to the new session before retrying.

Example fix

// before
await subscribeToPush(capturedUserId);

// after
try {
  await subscribeToPush(capturedUserId);
} catch (err) {
  if (err instanceof Error && err.message === 'Authenticated account changed during push setup') {
    const currentId = getCurrentClerkUser()?.id;
    if (currentId) await subscribeToPush(currentId);
    return;
  }
  throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

import { getCurrentClerkUser } from '@/services/clerk';

const expectedUserId = getCurrentClerkUser()?.id;
if (!expectedUserId) return;
await subscribeToPush(expectedUserId); // captured at flow start, re-checked by every checkpoint

Type guard

function isAccountSwitchAbort(err: unknown): boolean {
  return err instanceof Error && err.message === 'Authenticated account changed during push setup';
}

Try / catch

try {
  await subscribeToPush(expectedUserId);
} catch (err) {
  if (isAccountSwitchAbort(err)) {
    cancelPushSetup(); // discard the crossed flow entirely
    const currentId = getCurrentClerkUser()?.id;
    if (currentId) restartPushSetup(currentId);
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: User signs out and signs in as another account in a different tab while the push-enable flow is between its checkpoints; an account switcher changes the Clerk user during the await of getClerkToken(); a component re-invokes subscribeToPush with a stale expectedUserId captured before the switch.

Common situations: Shared machines where users swap accounts with settings tabs open; multi-account QA workflows; Clerk session events firing mid-subscription on slow networks.

Understand the failure class

Related errors


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/be37341bed6b48a0. Report an issue: GitHub.