koala73/worldmonitor · error · Error
ECCC_COUNT_DRIFT
ECCC_COUNT_DRIFT
Error message
ECCC_COUNT_DRIFT
What it means
Within one status collection ('issued' or 'continued'), the loop records numberMatched from the first page and requires every subsequent page to report the identical value. If a later page's numberMatched differs from the previously seen value, the collection changed mid-pagination, offsets become meaningless, and the loop throws Error('ECCC_COUNT_DRIFT') to avoid publishing a mixed-in-time, possibly inconsistent alert set.
Solutions
- Retry the whole fetchEcccAlertFeatures() call after a short delay; drift is transient during active weather and a fresh run starts with a new baseline numberMatched.
- Check whether the ECCC API offers a snapshot/transaction timestamp parameter to pin the collection during pagination; if so, include it in ECCC_ALERTS_URLS.
- If drift is frequent, reduce wall-clock time per fetch (increase concurrency or page size where permitted) so pagination completes within one collection snapshot.
- Verify the upstream API's numberMatched semantics haven't changed (e.g. becoming per-page instead of per-collection) and update the validator if so.
Defensive patterns
Strategy: retry
Try / catch
async function fetchEcccWithRetry(opts, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
return await fetchEcccAlertFeatures(opts);
} catch (err) {
if (!String(err.message).includes('ECCC_COUNT_DRIFT') || i === attempts - 1) throw err;
await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
}
}
} Prevention
- Schedule fetches away from peak alert churn where possible; drift is a live-data race.
- Treat a single drifted status as a partial result (result.partial) rather than a hard failure.
- Retry with backoff — a fresh run captures a new collection snapshot with a consistent numberMatched.
- If drift recurs persistently, investigate upstream load-balancer snapshot consistency rather than loosening the check.
When it happens
Trigger: A second or later page in scripts/_weather-alert-select.mjs:777 returns a data.numberMatched value different from the value captured on the first page of the same status — i.e. alerts were created, resolved, or expired between page requests while the sequential paging loop was running.
Common situations: Fetching during rapidly evolving severe weather where warnings are issued/expired every few seconds; slow fetchFn (network latency) widening the window for the live collection to mutate; an upstream API bug where numberMatched fluctuates or is computed inconsistently per page; load balancers serving different backend snapshots.
Related errors
- ECCC_PAGE_PROGRESS
- relay returned ${resp.status}
- COMPANY_MONITORING_ADMISSION_EVIDENCE_MISSING
- Sign in to view your brief.
- Authenticated account changed during push setup
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/255a1370aab60aaf.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/_weather-alert-select.mjs:777
let matched;
try {
do {
if (pages >= ECCC_MAX_PAGES) throw new Error('ECCC_PAGE_LIMIT');
if (byteBudget.remaining <= 0) throw new Error('ECCC_AGGREGATE_TOO_LARGE');
const url = new URL(ECCC_ALERTS_URLS[index]);
url.searchParams.set('offset', String(statusFeatures.length));
pages += 1;
const data = await fetchApprovedWeatherJson(url.toString(), {
allowedHosts: [ECCC_HOST], maxBytes, fetchFn, userAgent, byteBudget,
});
const page = requireAlertFeatures(data);
if (data.type !== 'FeatureCollection'
|| !Number.isSafeInteger(data.numberMatched) || data.numberMatched < 0
|| !Number.isSafeInteger(data.numberReturned) || data.numberReturned !== page.length
|| page.length > ECCC_PAGE_SIZE) {
throw new Error('ECCC_MALFORMED_PAGE');
}
if (matched !== undefined && data.numberMatched !== matched) throw new Error('ECCC_COUNT_DRIFT');
matched = data.numberMatched;
if (statusFeatures.length + page.length > matched
|| (page.length === 0 && statusFeatures.length < matched)) {
throw new Error('ECCC_PAGE_PROGRESS');
}
for (const feature of page) {
if (typeof feature?.id !== 'string' || !feature.id.trim()) throw new Error('ECCC_INVALID_ID');
if (seenIds.has(feature.id)) throw new Error('ECCC_DUPLICATE_ID');
seenIds.add(feature.id);
}
statusFeatures.push(...page);
} while (statusFeatures.length < matched);
features.push(...statusFeatures);
} catch (err) {
failures.push(err);
failedStatuses.push(status);
}
}View on GitHub (pinned to 7d06c8633d)