koala73/worldmonitor · error · Error
ECCC_INVALID_ID
ECCC_INVALID_ID
Error message
ECCC_INVALID_ID
What it means
ECCC_INVALID_ID is thrown while paginating ECCC weather-alert GeoJSON: every feature in a fetched page must carry a non-empty string `id`. The script throws immediately when a feature's id is missing, not a string, or blank after trim, because downstream dedup and status matching key off feature ids. It is a strict data-integrity guard against malformed upstream payloads.
Solutions
- Inspect the offending page payload and confirm each feature has a non-empty string `id`; if ECCC changed its schema, update the parsing/validation to the new id field
- Check which status (issued vs continued) and page index failed by logging the URL and page before the throw, then replay that request to confirm it is reproducible upstream rather than a transient payload
- Add a pre-loop validation or per-feature fallback (e.g. derive id from properties) only if ECCC legitimately emits id-less features, and document the deviation
- If caused by a stub/fixture in tests, fix the fixture so every feature has a string id
Example fix
// before
for (const feature of page) {
if (typeof feature?.id !== 'string' || !feature.id.trim()) throw new Error('ECCC_INVALID_ID');
...
}
// after
for (const feature of page) {
if (typeof feature?.id !== 'string' || !feature.id.trim()) {
console.error('ECCC feature missing id', JSON.stringify(feature).slice(0, 200));
throw new Error('ECCC_INVALID_ID');
}
...
} Defensive patterns
Strategy: validation
Validate before calling
function hasValidIds(features) {
return Array.isArray(features) && features.every((f) => typeof f?.id === 'string' && f.id.trim().length > 0);
}
if (!hasValidIds(page)) throw new Error('ECCC_INVALID_ID'); Type guard
const isFeatureWithId = (f) => typeof f?.id === 'string' && f.id.trim().length > 0;
Try / catch
try {
await fetchEcccPages();
} catch (err) {
if (err.message === 'ECCC_INVALID_ID') {
console.error('Upstream ECCC payload contained a feature without a valid id; skipping cycle');
}
} Prevention
- Validate fixtures and stubs against the same id contract as live payloads
- Log the raw feature before throwing so schema regressions are diagnosable
- Watch ECCC API changelogs for GeoJSON schema changes
- Fail the whole page, not just the feature, to avoid partial ingests
When it happens
Trigger: A page from the ECCC alerts API (issued or continued status fetch) contains a feature where `feature.id` is undefined/null, a non-string (e.g. number), or a whitespace-only string; iteration happens inside the do/while loop collecting statusFeatures until numberMatched is reached.
Common situations: ECCC changes or drops the `id` property in a GeoJSON response schema; a proxy or cache returns a partially transformed payload; a test stub fixture omits ids; an unexpected HTML/error page parsed leniently yields feature-like objects without ids.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- ECCC_DUPLICATE_ID
- Physical divergence composite repeats a metal weight
- Physical divergence snapshot has an invalid envelope
- Physical divergence snapshot must contain gold and silver re
- invalid_cursor
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/8cc417ce1076f0c9.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/_weather-alert-select.mjs:784
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);
}
}
if (failures.length === ECCC_LIVE_STATUSES.length) {
const detail = failures.map((err) => err?.message || String(err)).join('; ');
throw new Error(`ECCC issued and continued fetches both failed: ${detail}`);
}
// Returns an OBJECT, not a bare array, so a partial fetch cannot be consumed
// as if it were the whole set. `issued` and `continued` are separate collections
// and each carries alerts the other does not: `continued` is where an ONGOINGView on GitHub (pinned to 7d06c8633d)