koala73/worldmonitor · error · Error
Invalid PortWatch canonical snapshot
Error message
Invalid PortWatch canonical snapshot
What it means
On startup the PortWatch seed script reads its canonical snapshot (the list of ISO2 country codes seeded last time) from the seed snapshot store in strict mode. If the stored value is not null and is not an array of valid two-letter uppercase ISO2 codes, the script refuses to continue with 'Invalid PortWatch canonical snapshot', because the previous canonical state cannot be trusted for diffing/cleanup.
Solutions
- Delete/reset the canonical snapshot for CANONICAL_KEY so the seed rebuilds state from scratch
- Re-derive the canonical ISO2 list from current cache keys (KEY_PREFIX*) and rewrite the snapshot
- Verify the snapshot writer still emits string arrays of ISO2 codes matching /^[A-Z]{2}$/
- Restore the snapshot from a known-good backup taken after a successful seed
Example fix
// before (hand-edited snapshot) ["us", "de", "jp"] // after ["US", "DE", "JP"]
Defensive patterns
Strategy: validation
Validate before calling
const prev = await readSeedSnapshot(CANONICAL_KEY, { strict: false });
if (prev !== null && (!Array.isArray(prev) || prev.some((c) => typeof c !== 'string' || !/^[A-Z]{2}$/.test(c)))) {
await clearSeedSnapshot(CANONICAL_KEY);
} Type guard
const isValidIso2List = (v) => v === null || (Array.isArray(v) && v.every((c) => typeof c === 'string' && /^[A-Z]{2}$/.test(c))); Try / catch
try {
await runSeed();
} catch (err) {
if (err.message === 'Invalid PortWatch canonical snapshot') {
await resetCanonicalSnapshot();
await runSeed();
} else throw err;
} Prevention
- Never hand-edit seed snapshots
- Wrap snapshot writes in atomic operations to avoid partial writes
- Validate snapshot shape at write time, not only at read time
When it happens
Trigger: readSeedSnapshot(CANONICAL_KEY, { strict: true }) returns a value that is not an array, or an array containing entries that are not strings matching /^[A-Z]{2}$/ — e.g. corrupted snapshot storage, hand-edited seed metadata, or a snapshot written by a different schema.
Common situations: Snapshot file/Redis entry corrupted by an interrupted write; someone manually edited the canonical snapshot; a refactor changed the snapshot shape (e.g. storing objects instead of ISO2 strings) while old data lingers; wrong key restored from backup.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Physical divergence composite repeats a metal weight
- Physical divergence evaluation clock is invalid
- Physical divergence snapshot has an invalid envelope
- Invalid PortWatch seed metadata
- ${label} HTTP 400
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/d29e53bdafc8ad83.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/seed-portwatch-port-activity.mjs:1741
await extendExistingTtl([CANONICAL_KEY, META_KEY, ...prevCountryKeys], TTL);
if (previousRead) await publishPortActivitySnapshot({
countryData: new Map(),
canonicalAdvances: false,
metaPayload: buildPortActivityFailureMeta(previousMeta, { reason: new Error('SIGTERM') }),
});
} catch {}
try { await releaseLock(LOCK_DOMAIN, runId); } catch {}
process.exit(1);
};
process.on('SIGTERM', onSigterm);
process.on('SIGINT', onSigterm);
try {
const prevIso2List = await readSeedSnapshot(CANONICAL_KEY, { strict: true });
previousMeta = await readSeedSnapshot(META_KEY, { strict: true });
if (prevIso2List !== null && (!Array.isArray(prevIso2List)
|| prevIso2List.some((iso2) => typeof iso2 !== 'string' || !/^[A-Z]{2}$/.test(iso2)))) {
throw new Error('Invalid PortWatch canonical snapshot');
}
if (previousMeta !== null && (typeof previousMeta !== 'object' || Array.isArray(previousMeta))) {
throw new Error('Invalid PortWatch seed metadata');
}
previousRead = true;
prevCountryKeys = Array.isArray(prevIso2List) ? prevIso2List.map(iso2 => `${KEY_PREFIX}${iso2}`) : [];
console.log(` Fetching port activity data (60d: last30 + prev30 windows)...`);
const {
countries,
countryData,
servedStaleCount,
droppedTooOldCount,
droppedNoCacheCount,
freshFetchedCount,
cacheHitCount,
retryState,
coverage,View on GitHub (pinned to 7d06c8633d)