koala73/worldmonitor · error · Error
PortWatch cache read failed
Error message
PortWatch cache read failed
What it means
Thrown by redisMgetJson when an individual per-key result in the batched GET is malformed: it has an error property, or lacks a result property entirely. Each Upstash pipeline entry must be an object like {result: ...} or {error: ...}; anything else means the response entry cannot be interpreted as a GET outcome.
Solutions
- Log the failing entry (index and raw value) to see the embedded error or actual type
- Run TYPE on one of the failing keys to check for WRONGTYPE conflicts between writers
- Delete/flush the affected key namespace and re-run the seed to rewrite values as JSON strings
- Ensure all writers to these keys serialize with JSON.stringify (string values only)
- Align the key prefix/schema with the reader so no foreign keys are requested
Example fix
// before
return results.map((r) => {
if (r?.error || !Object.hasOwn(r ?? {}, 'result')) throw new Error('PortWatch cache read failed');
// after
return results.map((r, i) => {
if (r?.error || !Object.hasOwn(r ?? {}, 'result')) {
throw new Error(`PortWatch cache read failed for key ${keys[i]}: ${JSON.stringify(r)?.slice(0, 120)}`);
} Defensive patterns
Strategy: validation
Validate before calling
const keyResultsAreWellFormed = (results) => Array.isArray(results) && results.every((r) => r !== null && typeof r === 'object' && (Object.hasOwn(r, 'result') || Object.hasOwn(r, 'error')));
Type guard
const isUpstashGetEntry = (r) => typeof r === 'object' && r !== null && Object.hasOwn(r, 'result') && !r.error;
Try / catch
try {
const cache = await redisMgetJson(keys);
} catch (err) {
if (err.message.includes('cache read failed')) {
console.error('Entry-level Upstash error (check for WRONGTYPE): inspect key types before rerun');
throw err;
}
throw err;
} Prevention
- Serialize all writers to these keys with JSON.stringify so values are strings, not hashes/lists
- Run TYPE on suspect keys before reading to catch WRONGTYPE conflicts
- Include key names in error messages to make failing entries diagnosable
- Namespace keys per seed version and flush stale namespaces after schema changes
When it happens
Trigger: One or more entries of the Upstash /pipeline response contain {error:'...'} (e.g. WRONGTYPE if the key holds a non-string value like a hash or list), or a null/undefined entry where {result:...} was expected — often from an API change or a key written by a different code path with a different type.
Common situations: Keys written by an older seed version as hashes/lists instead of JSON strings; Upstash returning command-level errors inside an otherwise-200 pipeline; stale/mismatched database where the key namespace holds different data types; typo'd key prefix hitting entries of another feature.
Related errors
- PortWatch cache read returned incomplete results
- Redis EVAL failed: ${data.error}
- cache_all_null
- cache_all_null
- Redis command error: ${data.error}
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/0e1542ef73269dd7.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/seed-portwatch-port-activity.mjs:1040
return results;
}
const CORRUPT_COUNTRY_CACHE = Symbol('corrupt country cache');
// MGET-style batch read via the Upstash REST /pipeline endpoint. Returns an
// array aligned with `keys` where each element is either the parsed JSON
// payload, explicit miss, or confirmed corrupt value. Transport/envelope errors
// remain fatal: only a validated upstream replacement may overwrite corruption.
// Primes the per-country cache lookup in one round-trip instead of 174 GETs.
async function redisMgetJson(keys) {
if (keys.length === 0) return [];
const commands = keys.map((k) => ['GET', k]);
const results = await redisPipeline(commands);
if (!Array.isArray(results) || results.length !== keys.length) {
throw new Error('PortWatch cache read returned incomplete results');
}
return results.map((r) => {
if (r?.error || !Object.hasOwn(r ?? {}, 'result')) throw new Error('PortWatch cache read failed');
if (r.result === null) return null;
if (typeof r.result !== 'string') throw new Error('PortWatch cache read returned invalid result');
try {
const payload = JSON.parse(r.result);
return payload && typeof payload === 'object' && !Array.isArray(payload)
? payload : CORRUPT_COUNTRY_CACHE;
} catch {
return CORRUPT_COUNTRY_CACHE;
}
});
}
// fetchAll() — pure data collection, no Redis writes.
// Returns { countries: string[], countryData: Map<iso2, payload>, fetchedAt: string }.
//
// `progress` (optional) is mutated in-place so a SIGTERM handler in main()
// can report which batch / country we died on.
//View on GitHub (pinned to 7d06c8633d)