koala73/worldmonitor · error

saskalert: active entry count exceeds ${MAX_CAP_FETCHES}

Error message

saskalert: active entry count exceeds ${MAX_CAP_FETCHES}

What it means

fetchSaskAlerts throws this when the parsed Saskatchewan alert feed contains more active (non-ended) entries than MAX_CAP_FETCHES, the hard cap on how many CAP documents the fetch phase will hydrate. It is a guard against anomalous feed growth (or a malformed/hostile feed) that would otherwise trigger an unbounded fan-out of per-alert CAP fetches. The error aborts the whole fetch rather than silently truncating alerts.

Solutions

  1. Raise MAX_CAP_FETCHES in scripts/lib/saskalert.mjs if legitimate Saskatchewan activity genuinely exceeds it, and adjust the CAP-phase budget (capBudgetMs) accordingly.
  2. Inspect the feed: if ended/expired entries are being counted as active, fix isEndedSummaryEntry to recognize the new ended-marker format.
  3. Confirm the feed URL is the official SASKALERT_FEED_URL and not a fixture returning an unrealistic entry set.
  4. If the cap is intentional, treat this as a signal to degrade gracefully: the caller's freshness metadata will show the source never published, so consider pagination or batching of CAP hydration.
  5. Do not widen the cap without also reviewing MAX_ALERTS, CAP_CONCURRENCY, and CAP_PHASE_BUDGET_MS downstream limits.

Example fix

// before
if (active.length > MAX_CAP_FETCHES) {
  throw new Error(`saskalert: active entry count exceeds ${MAX_CAP_FETCHES}`);
}
// after
if (active.length > MAX_CAP_FETCHES) {
  console.warn(`saskalert: active entries ${active.length} exceed cap ${MAX_CAP_FETCHES}; hydrating first ${MAX_CAP_FETCHES} by severity`);
  active.length = MAX_CAP_FETCHES;
}
Defensive patterns

Strategy: validation

Validate before calling

const res = await fetch(feedUrl); const entries = (await res.json()); const active = entries.filter((e) => !isEndedSummaryEntry(e)); if (active.length > MAX_CAP_FETCHES) console.warn(`feed unusually large: ${active.length} active entries`);

Try / catch

try {
  const { alerts } = await fetchSaskAlerts();
} catch (err) {
  if (/active entry count exceeds/.test(err?.message)) {
    console.error('SK feed exceeded CAP fetch cap; check for outbreak or feed schema regression');
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling fetchSaskAlerts (directly or via the saskalert seed worker) when entries.filter(!isEndedSummaryEntry).length > MAX_CAP_FETCHES — e.g. a mass-alert day in Saskatchewan producing more concurrent active alerts than the cap, or a feed parse returning junk that all counts as active.

Common situations: Severe weather outbreak (many simultaneous warnings) exceeding the cap constant; upstream feed schema change making ended-entry detection fail so stale entries count as active; a test/staging URL pointing at an oversized fixture.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/04b17bffacebc0aa. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/saskalert.mjs:275

    throw new Error(`saskalert: host is not on the allowlist (${SASKALERT_HOST})`);
  }
  const fetchFn = opts.fetchFn ?? globalThis.fetch;
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
  const maxBytes = opts.maxBytes ?? MAX_PAYLOAD_BYTES;
  const userAgent = opts.userAgent || CHROME_UA;
  const nowMs = opts.nowMs ?? Date.now();

  const resp = await fetchFn(url, {
    headers: { Accept: 'application/json', 'User-Agent': userAgent },
    signal: AbortSignal.timeout(timeoutMs),
    redirect: 'error',
  });
  if (!resp.ok) throw new Error(`saskalert: HTTP ${resp.status}`);
  const entries = parseSaskAlertFeed(await readLimitedJson(resp, maxBytes, 'feed'));

  const active = entries.filter((entry) => !isEndedSummaryEntry(entry));
  if (active.length > MAX_CAP_FETCHES) {
    throw new Error(`saskalert: active entry count exceeds ${MAX_CAP_FETCHES}`);
  }

  const verification = { attempted: 0, failed: 0, skippedDeadline: 0, reasons: {} };
  const unverifiedIds = new Set();
  const failedLinks = new Set();
  const alerts = [];
  const seen = new Set();
  const seenCapLinks = new Set();
  const wallStart = opts.nowWallMs ?? Date.now();
  const budgetMs = opts.capBudgetMs ?? CAP_PHASE_BUDGET_MS;
  const concurrency = Math.max(1, opts.capConcurrency ?? CAP_CONCURRENCY);
  let nextIndex = 0;

  async function hydrateOne(entry) {
    const rememberFailure = (reason) => {
      unverifiedIds.add(`sk-saskalert-${String(entry.identifier || entry.id || '').trim()}`);
      verification.reasons[reason] = (verification.reasons[reason] || 0) + 1;
    };

View on GitHub (pinned to 7d06c8633d)