{"record":{"id":"ae613172c3bb7cb7","repo":"koala73/worldmonitor","slug":"seed-unavailable-key","errorCode":null,"errorMessage":"Seed unavailable: ${key}","messagePattern":"Seed unavailable: (.+?)","errorType":"exception","errorClass":"SeedUnavailableError","httpStatus":503,"severity":"error","filePath":"server/worldmonitor/market/v1/premium-stock-store.ts","lineNumber":172,"sourceCode":"    if (index.length !== 1 || index[0]?.error || !Array.isArray(ids)\n      || !ids.every(id => typeof id === 'string')) throw new SeedUnavailableError(indexKey);\n    if (ids.length === 0) {\n      out[symbol] = [];\n      return;\n    }\n    const records = await runRedisPipeline(ids.map(id => ['GET', analysisItemKey(String(id))]));\n    if (records.length !== ids.length) throw new SeedUnavailableError(indexKey);\n    out[symbol] = records.flatMap(record => {\n      if (record.error) throw new SeedUnavailableError(indexKey);\n      if (record.result === null) return [];\n      if (typeof record.result !== 'string') throw new SeedUnavailableError(indexKey);\n      let snapshot: AnalyzeStockResponse;\n      try {\n        snapshot = JSON.parse(record.result);\n      } catch {\n        throw new SeedUnavailableError(indexKey);\n      }\n      if (!snapshot || typeof snapshot !== 'object') throw new SeedUnavailableError(indexKey);\n      return snapshot.available ? [snapshot] : [];\n    }).sort(compareAnalysisDesc);\n  }));\n\n  return out;\n}\n\nexport async function storeHistoricalBacktestAnalysisRecords(\n  snapshots: AnalyzeStockResponse[],\n): Promise<void> {\n  const commands: Array<Array<string | number>> = [];\n  const touchedSymbols = new Set<string>();\n\n  for (const snapshot of snapshots) {\n    const record = normalizeLedgerRecord(snapshot);\n    if (!record) continue;\n\n    const indexKey = backtestLedgerIndexKey(record.symbol);","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/server/worldmonitor/market/v1/premium-stock-store.ts#L154-L190","documentation":"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.","triggerScenarios":"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>'.","commonSituations":"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.","solutions":["Read the key name from the error message and inspect it directly (EXISTS/TYPE/ZCARD) in Redis.","Run the premium stock analysis seeder for the affected symbols.","If keys expire, adjust TTLs or add a periodic re-seed job.","Wrap the history handler to return an empty/degraded result with a seed-status health signal if the dashboard should render without seed data."],"exampleFix":"// before: handler propagates SeedUnavailableError to the client\nreturn getStoredStockAnalysisHistory(req.symbols, limit, includeNews);\n// after: degrade gracefully with logged seed status\ntry {\n  return await getStoredStockAnalysisHistory(req.symbols, limit, includeNews);\n} catch (e) {\n  if (e instanceof SeedUnavailableError) {\n    logger.warn('seed unavailable', { key: e.message });\n    return { analyses: {}, seedStatus: 'unavailable' };\n  }\n  throw e;\n}","handlingStrategy":"fallback","validationCode":"// pre-flight seed health probe\nconst healthy = await Promise.all(symbols.map(s => redis.exists(analysisHistoryIndexKey(s, includeNews))));","typeGuard":null,"tryCatchPattern":"try {\n  out = await getStoredStockAnalysisHistory(symbols, limit, includeNews);\n} catch (e) {\n  if (e instanceof SeedUnavailableError) {\n    metrics.seedUnavailable(e.message);\n    out = Object.fromEntries(symbols.map(s => [s, []]));\n  } else throw e;\n}","preventionTips":["Run the seeder as part of deployment, before the API accepts traffic.","Export a seed-status health key and alert on 'unavailable'.","Log the key name from the error message to speed up diagnosis."],"tags":["redis","seed-data","cache-miss"],"backgroundTag":"empty-result-set","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-22T01:50:49.965Z","contentChangedAt":"2026-09-22T01:50:49.965Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}