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

  1. Run the premium stock analysis seeder to populate the ZREVRANGE index for the requested symbols.
  2. Verify Redis/Upstash connectivity and credentials (loadEnvFile) — pipeline error entries usually mean connection or auth problems.
  3. Check that the seeder and reader use the same key prefix/version (analysisHistoryIndexKey) and that includeNews matches what the seeder wrote.
  4. 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

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)