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

  1. Delete/reset the canonical snapshot for CANONICAL_KEY so the seed rebuilds state from scratch
  2. Re-derive the canonical ISO2 list from current cache keys (KEY_PREFIX*) and rewrite the snapshot
  3. Verify the snapshot writer still emits string arrays of ISO2 codes matching /^[A-Z]{2}$/
  4. 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

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


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)