{"record":{"id":"a803e0ba7ec8ee5e","repo":"koala73/worldmonitor","slug":"sign-in-to-view-your-brief","errorCode":null,"errorMessage":"Sign in to view your brief.","messagePattern":"Sign in to view your brief\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/components/LatestBriefPanel.ts","lineNumber":358,"sourceCode":"    this.gateLocked = false;\n    super.unlockPanel();\n    if (wasLocked) {\n      this.renderLoading();\n      void this.refresh();\n    }\n  }\n\n  private async fetchLatest(signal: AbortSignal): Promise<LatestBriefResponse> {\n    // /api/latest-brief is user-scoped and Bearer-only. premiumFetch\n    // short-circuits on desktop WORLDMONITOR_API_KEY / tester keys\n    // and never sends Clerk, producing a 401 we can't recover from.\n    // Always mint a fresh Bearer here — the refresh() pre-check\n    // guaranteed authState.user exists.\n    const token = await getClerkToken();\n    if (!token) {\n      // Clerk token evicted between the pre-check and now (logout,\n      // cache expiry + Clerk session gone). Surface as sign-in.\n      throw new Error('Sign in to view your brief.');\n    }\n    const res = await fetch(LATEST_BRIEF_ENDPOINT, {\n      signal,\n      headers: { Authorization: `Bearer ${token}` },\n    });\n    // 401/403 are classified rather than assumed. `/api/latest-brief`\n    // returns 403 for BOTH a free plan (`pro_required`) and a rejected\n    // origin (`Origin not allowed`), and a `pro_required` the client's own\n    // entitlement state contradicts is a server-side desync — rendering\n    // any of those as \"Upgrade to Pro\" tells a paying user to buy the\n    // plan they already bought (#5608).\n    // classifyDenialResponse reads the body ONLY on a denial status, so\n    // res.json() below still has an unconsumed stream on the success path.\n    const verdict = await classifyDenialResponse(res, readClientEntitlementBelief(getAuthState()));\n    if (verdict !== null) {\n      // Reading the body is awaited, so a gate-lock or account-switch abort\n      // can land mid-parse — where readDenialErrorCode swallows it. Without\n      // this, that abort would surface as a denial render instead of the","sourceCodeStart":340,"sourceCodeEnd":376,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/src/components/LatestBriefPanel.ts#L340-L376","documentation":"Thrown by LatestBriefPanel.fetchLatest() when getClerkToken() returns null even though a refresh() pre-check had guaranteed an authenticated user. The comment documents the race: the Clerk token was evicted between the pre-check and the mint (logout, or cache expiry with the Clerk session gone). The panel deliberately always mints a fresh Bearer because premiumFetch would short-circuit on desktop API keys and send no Clerk header, producing an unrecoverable 401.","triggerScenarios":"/api/latest-brief is requested; between the auth pre-check and getClerkToken() the user logs out, the Clerk session expires, or the token cache is evicted (another tab logged out). The null token converts to this 'Sign in to view your brief.' error rather than firing an unauthenticated request.","commonSituations":"Logout in another tab while the brief panel refreshes; Clerk session TTL expiring mid-session on slow networks; multi-tab session sync evicting tokens; desktop app builds where the auth path differs.","solutions":["Catch this message and render the sign-in call-to-action — it is the panel's intended UX signal, not a bug to log","Before retrying, re-run the auth refresh flow: ensure Clerk is loaded and authState.user still exists, then re-enter refresh()","In multi-tab apps, listen for Clerk session change events and cancel in-flight brief fetches on sign-out so the race window closes"],"exampleFix":"// before\nconst token = await getClerkToken();\nif (!token) throw new Error('Sign in to view your brief.'); // raw 401 path would follow without this\n\n// after (same guard, but caller branches explicitly):\ntry {\n  const brief = await panel.fetchLatest(signal);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Sign in to view your brief.') {\n    renderSignInPrompt(); // intended UX, not an error report\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":"const auth = getAuthState();\nif (!auth.user) { renderSignInPrompt(); return; } // pre-check before fetchLatest","typeGuard":"function hasClerkUser(auth: { user: unknown }): boolean { return auth.user != null; }","tryCatchPattern":"catch (e) { if (e instanceof Error && e.message === 'Sign in to view your brief.') renderSignInPrompt(); else throw e; }","preventionTips":["Treat this message as an intended UX signal — render sign-in, not an error report","Cancel in-flight brief fetches on Clerk sign-out/session-change events to close the race","In multi-tab apps, sync session state before refreshing the panel"],"tags":["auth","clerk","race-condition","latest-brief"],"backgroundTag":"missing-auth-token","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}