koala73/worldmonitor · error · Error

Brief service unavailable (${res.status})

Error message

Brief service unavailable (${res.status})

What it means

Thrown by LatestBriefPanel.fetchLatest() when the response to /api/latest-brief is non-OK and was NOT classified as a denial. The classification step (classifyDenialResponse on 401/403, covering free-plan 'pro_required' and rejected origins) runs first and throws BriefAccessError instead — so reaching this message means a status outside that contract: typically 5xx from the brief composer, 429 rate limiting, or 502/504 from a gateway.

Source

Thrown at src/components/LatestBriefPanel.ts:383

    // 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
      // no-op the abort was asking for.
      if (signal.aborted) throw new DOMException('aborted while reading denial body', 'AbortError');
      if (verdict === 'entitlement_desync') reportEntitlementDesync('latest-brief');
      throw new BriefAccessError(verdict);
    }
    if (!res.ok) {
      throw new Error(`Brief service unavailable (${res.status})`);
    }
    const body = (await res.json()) as LatestBriefResponse;
    if (!body || (body.status !== 'ready' && body.status !== 'composing')) {
      throw new Error('Unexpected response from brief service');
    }
    return body;
  }

  private renderLoading(): void {
    clearChildren(this.content);
    this.content.appendChild(
      h('div', { className: 'latest-brief-empty' },
        h('div', { className: 'latest-brief-empty-title' }, 'Loading your brief…'),
      ),
    );
  }

  /**

View on GitHub (pinned to eeab0a219f)

Solutions

  1. Retry with backoff for 5xx/429 — the brief endpoint is transient-failure prone; abort any in-flight fetch first via the existing signal
  2. Check the brief service health endpoint and recent deploys if the status persists
  3. Capture res.status in telemetry: distinguishing 429 (back off longer) from 503 (redeploy) changes the retry policy

Example fix

// before
if (!res.ok) throw new Error(`Brief service unavailable (${res.status})`); // caller shows generic error

// after (caller-side retry policy keyed on status):
catch (e) {
  if (e instanceof Error && e.message.startsWith('Brief service unavailable (')) {
    const status = Number(e.message.slice(-4, -1));
    if (status === 429 || status >= 500) await retryWithBackoff(() => panel.refresh());
    else showError(e);
  }
}
Defensive patterns

Strategy: retry

Validate before calling

// nothing client-side can pre-validate a 5xx; gate the retry on prior knowledge:
if (briefServiceHealthy === false) return; // skip refresh while known unhealthy

Try / catch

catch (e) { const m = /^Brief service unavailable \((\d+)\)$/.exec(e instanceof Error ? e.message : ''); if (m) { const status = Number(m[1]); if (status === 429 || status >= 500) await retryWithBackoff(refresh, { signal }); else showError(e); } else throw e; }

Prevention

When it happens

Trigger: GET /api/latest-brief returns 500 (brief generation crashed), 502/503 (Railway service down or redeploying), 429 (user or shared quota throttled), or any other non-OK status that is not a 401/403 denial.

Common situations: Brief service deployment in progress; upstream LLM/composition dependency failing; a user hammering refresh hitting rate limits; Vercel function timeout surfacing as 504.

Understand the failure class

Related errors


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