koala73/worldmonitor · error · SeedUnavailableError

Seed unavailable

Error message

Seed unavailable: ${key}

What it means

The top-level SeedUnavailableError variant whose message includes the actual Redis key. It is thrown from getStoredStockAnalysisHistory when any per-symbol seed read fails (missing index, missing items, non-string values, or unparsable/non-object snapshots). The message names the offending index key so operators can inspect that exact key.

Solutions

  1. Read the key name from the error message and inspect it directly (EXISTS/TYPE/ZCARD) in Redis.
  2. Run the premium stock analysis seeder for the affected symbols.
  3. If keys expire, adjust TTLs or add a periodic re-seed job.
  4. Wrap the history handler to return an empty/degraded result with a seed-status health signal if the dashboard should render without seed data.

Example fix

// before: handler propagates SeedUnavailableError to the client
return getStoredStockAnalysisHistory(req.symbols, limit, includeNews);
// after: degrade gracefully with logged seed status
try {
  return await getStoredStockAnalysisHistory(req.symbols, limit, includeNews);
} catch (e) {
  if (e instanceof SeedUnavailableError) {
    logger.warn('seed unavailable', { key: e.message });
    return { analyses: {}, seedStatus: 'unavailable' };
  }
  throw e;
}
Defensive patterns

Strategy: fallback

Validate before calling

// pre-flight seed health probe
const healthy = await Promise.all(symbols.map(s => redis.exists(analysisHistoryIndexKey(s, includeNews))));

Try / catch

try {
  out = await getStoredStockAnalysisHistory(symbols, limit, includeNews);
} catch (e) {
  if (e instanceof SeedUnavailableError) {
    metrics.seedUnavailable(e.message);
    out = Object.fromEntries(symbols.map(s => [s, []]));
  } else throw e;
}

Prevention

When it happens

Trigger: Any call to history (getStoredStockAnalysisHistory) where at least one requested symbol's analysis seed data is missing or corrupt in Redis; the Promise.all rejects with the message 'Seed unavailable: <indexKey>'.

Common situations: Fresh Redis instance without seeding; expired/evicted keys; seeder not run for newly requested symbols; Upstash REST errors; version-skewed key names between seeder and API.

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/ae613172c3bb7cb7. Report an issue: GitHub.

Appendix: source

Thrown at server/worldmonitor/market/v1/premium-stock-store.ts:172

    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] : [];
    }).sort(compareAnalysisDesc);
  }));

  return out;
}

export async function storeHistoricalBacktestAnalysisRecords(
  snapshots: AnalyzeStockResponse[],
): Promise<void> {
  const commands: Array<Array<string | number>> = [];
  const touchedSymbols = new Set<string>();

  for (const snapshot of snapshots) {
    const record = normalizeLedgerRecord(snapshot);
    if (!record) continue;

    const indexKey = backtestLedgerIndexKey(record.symbol);

View on GitHub (pinned to e586b8b4b8)