{"record":{"id":"f77fc9e069708bbf","repo":"koala73/worldmonitor","slug":"authentication-unavailable-while-loading-business","errorCode":null,"errorMessage":"Authentication unavailable while loading Business Pro seats. Try again.","messagePattern":"Authentication unavailable while loading Business Pro seats\\. Try again\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/billing.ts","lineNumber":496,"sourceCode":"  ownerDomain: string | null;\n  ownerIsCorporateDomain: boolean;\n  seats: BusinessSeat[];\n}\n\n/** List the caller's Business Pro seats. Only the owner sees their own grants. */\nexport async function listBusinessSeats(): Promise<ListBusinessSeatsResult> {\n  const userId = getCurrentClerkUser()?.id;\n  if (!userId) {\n    return { businessSubscriptionId: null, ownerDomain: null, ownerIsCorporateDomain: false, seats: [] };\n  }\n  const client = await getConvexClient();\n  const api = await getConvexApi();\n  if (!client || !api) {\n    return { businessSubscriptionId: null, ownerDomain: null, ownerIsCorporateDomain: false, seats: [] };\n  }\n  if (!await waitForConvexAuthForUser(userId)) {\n    assertAccountStillCurrent(userId, 'loading Business Pro seats');\n    throw new Error('Authentication unavailable while loading Business Pro seats. Try again.');\n  }\n  return settleAccountOperation(\n    userId,\n    'loading Business Pro seats',\n    () => client.query(api.payments.businessSeats.listSeats, {}),\n  );\n}\n\n/** Invite up to 4 same-domain teammates to Business Pro seats. */\nexport async function inviteBusinessSeats(emails: string[]): Promise<{\n  invited: Array<{ email: string; grantId: string; status: 'created' | 'already_pending' | 'already_accepted' }>;\n}> {\n  const userId = requireSignedInUserId('invite Business Pro seats');\n  const client = await getConvexClient();\n  const api = await getConvexApi();\n  if (!client || !api) throw new Error('Convex unavailable');\n  await requireCurrentConvexUser(userId, 'inviting Business Pro seats');\n  return settleAccountOperation(","sourceCodeStart":478,"sourceCodeEnd":514,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/src/services/billing.ts#L478-L514","documentation":"Thrown by listBusinessSeats() in src/services/billing.ts when the Convex client and API are available but waitForConvexAuthForUser(userId) returns false while the same Clerk user is still current. That helper waits up to 10 s for the Convex WebSocket auth barrier — the server-confirmed setAuth for this exact userId — and also fails if the barrier was superseded by a newer auth generation. The error means: signed in via Clerk, Convex reachable enough to construct a client, but Convex never confirmed this user's authentication in time, so the businessSeats.listSeats query is not issued.","triggerScenarios":"Opening the Business Pro seats UI within the first seconds after sign-in on a slow link (barrier not yet settled at the 10 s timeout); Clerk token fetch stalling so setAuth never completes; the Convex WebSocket being blocked or dropped by a corporate proxy/VPN; rapid sign-out/sign-in creating a newer auth generation that invalidates the waited barrier.","commonSituations":"Slow mobile networks right after login; locked-down enterprise networks that allow HTTPS but break WebSocket upgrades; Clerk dev-instance rate limits delaying token issuance; environments where VITE_CONVEX_URL points at a deployment that rejects the Clerk token (misconfigured auth provider).","solutions":["Retry the load once after a short delay — the barrier usually settles just after the timeout, and the second call typically succeeds.","In DevTools → Network → WS, confirm the wss://…convex.cloud socket connects and authenticates; a failing upgrade points to proxy/VPN interference.","Verify the Clerk keys and VITE_CONVEX_URL belong to the same deployment pairing (Convex auth configured for this Clerk instance).","If it reproduces deterministically for one user, check for auth-generation churn: rapid sign-out/sign-in loops that permanently supersede the waited barrier."],"exampleFix":"// before\nconst seats = await listBusinessSeats();\n\n// after\nlet seats;\ntry {\n  seats = await listBusinessSeats();\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Authentication unavailable')) {\n    await new Promise((r) => setTimeout(r, 1500));\n    seats = await listBusinessSeats();\n  } else {\n    throw err;\n  }\n}","handlingStrategy":"retry","validationCode":"import { waitForConvexAuthForUser } from '@/services/convex-client';\nimport { getCurrentClerkUser } from '@/services/clerk';\n\nconst userId = getCurrentClerkUser()?.id;\nif (!userId) return emptySeats;\nif (!await waitForConvexAuthForUser(userId, 15_000)) {\n  showAuthPendingUi();\n  return;\n}\nconst seats = await listBusinessSeats();","typeGuard":null,"tryCatchPattern":"try {\n  return await listBusinessSeats();\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Authentication unavailable')) {\n    await delay(1500); // barrier usually settles just past the 10s gate\n    return await listBusinessSeats();\n  }\n  throw err;\n}","preventionTips":["Trigger the seats load only after the Convex auth barrier settles (subscribe to auth-ready rather than racing it at mount).","Give corporate-network users a WebSocket health hint when the Convex socket fails.","Distinguish this message from 'Account changed while…' — the first is transient, the second requires restarting the flow."],"tags":["convex","clerk","authentication","websocket","timeout","retryable"],"backgroundTag":"auth-handshake-timeout","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}