koala73/worldmonitor · error · SeedUnavailableError
${PHYSICAL_PREMIUM_KEY}
Error message
${PHYSICAL_PREMIUM_KEY} What it means
getPhysicalPremiums throws SeedUnavailableError(PHYSICAL_PREMIUM_KEY) when readCachedJson reports an error status reading the physical premiums cache — i.e. Redis itself failed (connection, timeout, decode), not merely a miss. A clean miss instead degrades to an empty premiums list.
Solutions
- Verify Upstash URL/token via loadEnvFile and test connectivity with a PING
- Check logCacheReadError/readCachedJson error details for the Redis failure cause
- Retry the request — transient Redis errors clear on their own
- Confirm the seed producer is healthy so the key exists with a valid payload
Defensive patterns
Strategy: retry
Validate before calling
const probe = await redis.ping();
if (probe !== 'PONG') throw new Error('redis unreachable before physical premiums read'); Try / catch
try {
return await api.getPhysicalPremiums(req);
} catch (e) {
if (isSeedUnavailableError(e)) {
// Redis read error: brief retry, then degrade to empty premiums
return await withRetry(() => api.getPhysicalPremiums(req), { attempts: 2, fallback: { premiums: [] } });
}
throw e;
} Prevention
- Validate Upstash credentials in every environment via loadEnvFile
- Monitor Redis error rates (timeouts, auth, eviction)
- Ensure the physical premiums producer keeps the key warm
- Distinguish cache misses (empty result) from cache read errors
When it happens
Trigger: Redis/Upstash connection failure, timeout, auth error, or deserialization error while reading PHYSICAL_PREMIUM_KEY; e.g. wrong credentials, Redis maintenance, network partition from the worker.
Common situations: Missing or rotated Upstash credentials on a new environment; Upstash free-tier eviction/limits; transient network blips during the read.
Related errors
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22).
Data as JSON: /api/errors/d795070a70601b0d.
Report an issue: GitHub.
Appendix: source
Thrown at server/worldmonitor/market/v1/get-physical-premiums.ts:174
}
if (!metals.includes(metal)) metals.push(metal);
}
if (unsupported.length > 0) {
throw new ValidationError([{
field: 'metals',
description: `Unsupported metal${unsupported.length === 1 ? '' : 's'}: ${unsupported.join(', ')}`,
}]);
}
return metals;
}
export async function getPhysicalPremiums(
_ctx: ServerContext,
req: GetPhysicalPremiumsRequest,
): Promise<GetPhysicalPremiumsResponse> {
const metals = resolvePhysicalPremiumMetals(parseStringArray(req.metals));
const read = await readCachedJson(PHYSICAL_PREMIUM_KEY, true);
if (read.status === 'error') throw new SeedUnavailableError(PHYSICAL_PREMIUM_KEY);
try {
const raw = read.status === 'hit' ? read.value as RawPayload | null : null;
const fx = mapFx(raw?.fx);
if (!fx || !Array.isArray(raw?.premiums)) return { premiums: [] };
const premiums = raw.premiums.map((premium) => mapPremium(premium, fx.rate))
.filter((item): item is PhysicalPremium => item !== null);
if (premiums.length !== 2 || premiums.length !== raw.premiums.length) return { premiums: [] };
if (new Set(premiums.map((premium) => premium.metal)).size !== 2) return { premiums: [] };
const wanted = new Set(metals);
return {
premiums: metals.length === 0 ? premiums : premiums.filter((item) => wanted.has(item.metal)),
fx,
};
} catch {
return { premiums: [] };
}
}
View on GitHub (pinned to e586b8b4b8)