koala73/worldmonitor · warning · Error

Sign in to view your brief.

Error message

Sign in to view your brief.

What it means

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.

Source

Thrown at src/components/LatestBriefPanel.ts:358

    this.gateLocked = false;
    super.unlockPanel();
    if (wasLocked) {
      this.renderLoading();
      void this.refresh();
    }
  }

  private async fetchLatest(signal: AbortSignal): Promise<LatestBriefResponse> {
    // /api/latest-brief is user-scoped and Bearer-only. premiumFetch
    // short-circuits on desktop WORLDMONITOR_API_KEY / tester keys
    // and never sends Clerk, producing a 401 we can't recover from.
    // Always mint a fresh Bearer here — the refresh() pre-check
    // guaranteed authState.user exists.
    const token = await getClerkToken();
    if (!token) {
      // Clerk token evicted between the pre-check and now (logout,
      // cache expiry + Clerk session gone). Surface as sign-in.
      throw new Error('Sign in to view your brief.');
    }
    const res = await fetch(LATEST_BRIEF_ENDPOINT, {
      signal,
      headers: { Authorization: `Bearer ${token}` },
    });
    // 401/403 are classified rather than assumed. `/api/latest-brief`
    // returns 403 for BOTH a free plan (`pro_required`) and a rejected
    // origin (`Origin not allowed`), and a `pro_required` the client's own
    // entitlement state contradicts is a server-side desync — rendering
    // any of those as "Upgrade to Pro" tells a paying user to buy the
    // plan they already bought (#5608).
    // classifyDenialResponse reads the body ONLY on a denial status, so
    // res.json() below still has an unconsumed stream on the success path.
    const verdict = await classifyDenialResponse(res, readClientEntitlementBelief(getAuthState()));
    if (verdict !== null) {
      // Reading the body is awaited, so a gate-lock or account-switch abort
      // can land mid-parse — where readDenialErrorCode swallows it. Without
      // this, that abort would surface as a denial render instead of the

View on GitHub (pinned to eeab0a219f)

Solutions

  1. Catch this message and render the sign-in call-to-action — it is the panel's intended UX signal, not a bug to log
  2. Before retrying, re-run the auth refresh flow: ensure Clerk is loaded and authState.user still exists, then re-enter refresh()
  3. In multi-tab apps, listen for Clerk session change events and cancel in-flight brief fetches on sign-out so the race window closes

Example fix

// before
const token = await getClerkToken();
if (!token) throw new Error('Sign in to view your brief.'); // raw 401 path would follow without this

// after (same guard, but caller branches explicitly):
try {
  const brief = await panel.fetchLatest(signal);
} catch (e) {
  if (e instanceof Error && e.message === 'Sign in to view your brief.') {
    renderSignInPrompt(); // intended UX, not an error report
  } else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const auth = getAuthState();
if (!auth.user) { renderSignInPrompt(); return; } // pre-check before fetchLatest

Type guard

function hasClerkUser(auth: { user: unknown }): boolean { return auth.user != null; }

Try / catch

catch (e) { if (e instanceof Error && e.message === 'Sign in to view your brief.') renderSignInPrompt(); else throw e; }

Prevention

When it happens

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

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

Related errors


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