koala73/worldmonitor · error · SeedUnavailableError
${indexKey}
Error message
${indexKey} What it means
SeedUnavailableError is thrown when the Redis seed data backing the stock analysis history index is missing or structurally invalid. Here the ZREVRANGE read of the per-symbol analysis history index (analysisHistoryIndexKey) either errored, returned a malformed payload, or the pipeline produced an unexpected number of results. The store treats any seed corruption/absence as 'seed unavailable' so callers can distinguish missing seed data from an empty result.
Solutions
- Run the premium stock analysis seeder to populate the ZREVRANGE index for the requested symbols.
- Verify Redis/Upstash connectivity and credentials (loadEnvFile) — pipeline error entries usually mean connection or auth problems.
- Check that the seeder and reader use the same key prefix/version (analysisHistoryIndexKey) and that includeNews matches what the seeder wrote.
- ZSCAN the index key directly to confirm it exists and contains string member IDs.
Example fix
// before: reader fails when the index was seeded with a different includeNews flag
const indexKey = analysisHistoryIndexKey(symbol, includeNews);
// after: probe both variants or normalize the flag the seeder used
const indexKey = analysisHistoryIndexKey(symbol, includeNews);
let index = await runRedisPipeline([['ZREVRANGE', indexKey, 0, clampedLimit - 1]]);
if (index[0]?.error || !Array.isArray(index[0]?.result)) {
index = await runRedisPipeline([['ZREVRANGE', analysisHistoryIndexKey(symbol, !includeNews), 0, clampedLimit - 1]]);
} Defensive patterns
Strategy: try-catch
Validate before calling
// no client-side pre-check; verify server-side seed health before calling const ok = await redis.exists(analysisHistoryIndexKey(symbol, includeNews));
Try / catch
try {
const history = await getStoredStockAnalysisHistory(symbols, limit, includeNews);
} catch (e) {
if (e instanceof SeedUnavailableError) return fallbackHistory(symbols);
throw e;
} Prevention
- Seed before serving: gate the history endpoint on seed health checks.
- Keep seeder and reader key-building functions in one shared module.
- Monitor ZCARD on history indexes and alert when zero.
When it happens
Trigger: Calling getStoredStockAnalysisHistory (via the history RPC) for a symbol whose ZSET index key does not exist in Redis, or when the Redis pipeline returns an error entry or non-string member IDs.
Common situations: Redis was flushed or migrated without re-running the seeder; the seeder for this symbol never ran; Upstash connection errors surface as pipeline error entries; a key naming/version change made old indexes invisible.
Understand the failure class
Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.
Related errors
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22).
Data as JSON: /api/errors/e9cdec42007f4689.
Report an issue: GitHub.
Appendix: source
Thrown at server/worldmonitor/market/v1/premium-stock-store.ts:155
const ids = await zrevrange(indexKey, ANALYSIS_HISTORY_LIMIT + 4);
await trimIndexTail(indexKey, ids, ANALYSIS_HISTORY_LIMIT);
}
export async function getStoredStockAnalysisHistory(
symbols: string[],
includeNews: boolean,
limitPerSymbol = ANALYSIS_HISTORY_LIMIT,
): Promise<AnalysisHistoryRecord> {
const normalized = normalizeSymbolList(symbols);
const clampedLimit = Math.max(1, Math.min(ANALYSIS_HISTORY_LIMIT, limitPerSymbol));
const out: AnalysisHistoryRecord = {};
await Promise.all(normalized.map(async (symbol) => {
const indexKey = analysisHistoryIndexKey(symbol, includeNews);
const index = await runRedisPipeline([['ZREVRANGE', indexKey, 0, clampedLimit - 1]]);
const ids = index[0]?.result;
if (index.length !== 1 || index[0]?.error || !Array.isArray(ids)
|| !ids.every(id => typeof id === 'string')) throw new SeedUnavailableError(indexKey);
if (ids.length === 0) {
out[symbol] = [];
return;
}
const records = await runRedisPipeline(ids.map(id => ['GET', analysisItemKey(String(id))]));
if (records.length !== ids.length) throw new SeedUnavailableError(indexKey);
out[symbol] = records.flatMap(record => {
if (record.error) throw new SeedUnavailableError(indexKey);
if (record.result === null) return [];
if (typeof record.result !== 'string') throw new SeedUnavailableError(indexKey);
let snapshot: AnalyzeStockResponse;
try {
snapshot = JSON.parse(record.result);
} catch {
throw new SeedUnavailableError(indexKey);
}
if (!snapshot || typeof snapshot !== 'object') throw new SeedUnavailableError(indexKey);
return snapshot.available ? [snapshot] : [];View on GitHub (pinned to e586b8b4b8)