{"record":{"id":"81c552757e785cd3","repo":"koala73/worldmonitor","slug":"feed-digest-unavailable-for-variant-en","errorCode":null,"errorMessage":"Feed digest unavailable for ${variant}/en","messagePattern":"Feed digest unavailable for (.+?)/en","errorType":"exception","errorClass":"McpSourceUnavailableError","httpStatus":null,"severity":"error","filePath":"api/mcp/registry/nlp-tools.ts","lineNumber":192,"sourceCode":"): Promise<NlpDigestFetch> {\n  const digestUrl = `${base}/api/news/v1/list-feed-digest?variant=${variant}&lang=en`;\n  const auth = await buildAuthHeaders(context, 'GET', digestUrl, null);\n  const res = await fetch(digestUrl, {\n    headers: { ...auth, 'User-Agent': NLP_UA },\n    signal: AbortSignal.timeout(NLP_DIGEST_TIMEOUT_MS),\n  });\n  assertToolFetchOk(res, 'list-feed-digest');\n  const body = await res.json() as {\n    categories?: Record<string, NlpDigestCategoryGroup>;\n    feedStatuses?: Record<string, string>;\n    generatedAt?: string;\n  };\n\n  const seen = new Set<string>();\n  const items: NewsItemCore[] = [];\n  const categories = body.categories ?? {};\n  if (Object.keys(categories).length === 0 && Object.keys(body.feedStatuses ?? {}).length === 0) {\n    throw new McpSourceUnavailableError(\n      `Feed digest unavailable for ${variant}/en`,\n      [`news:digest:v1:${variant}:en`],\n      [],\n    );\n  }\n  const availableCategories = Object.keys(categories).sort();\n  let groups: NlpDigestCategoryGroup[];\n  let note: string | undefined;\n\n  if (!category) {\n    groups = Object.values(categories);\n  } else if (Object.prototype.hasOwnProperty.call(categories, category)) {\n    groups = [categories[category]!];\n  } else {\n    groups = [];\n    // Prefer the live snapshot keys so agents see what this cycle actually\n    // carries; fall back to the static enum when the digest is empty.\n    const listed = availableCategories.length > 0","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/api/mcp/registry/nlp-tools.ts#L174-L210","documentation":"Thrown by fetchNlpDigestItems() (shared by the extract_entities no-text mode and get_news_clusters tools) when the list-feed-digest endpoint returned HTTP 200 but the body contains zero category keys AND zero feedStatus keys — meaning the news digest has not been seeded for the requested variant/language combination. The cache key news:digest:v1:<variant>:en is reported as unavailable with no failed_inputs (this is a genuine miss, not a Redis read failure).","triggerScenarios":"Calling extract_entities or get_news_clusters with a variant whose digest is not yet seeded (e.g. 'tech' variant when only 'full' has been produced); calling right after a deploy before the NLP digest pipeline runs; requesting a variant that has no RSS sources configured. The endpoint returned 200 (it passed assertToolFetchOk) but with an empty payload.","commonSituations":"Variant mismatch — the MCP dispatch defaults to one variant but the digest producer ran for a different one; a digest pipeline outage where the NLP worker is down so no fresh digests are written; a Redis key naming drift where the producer writes a different key suffix than the reader expects.","solutions":["Verify the digest exists in Redis: check GET /api/health for the news:digest:v1:<variant>:en coverage and the corresponding seed-meta key.","Retry after the NLP digest cadence (~15 minutes) — the digest is produced on a recurring cycle and a transient gap resolves on the next run.","Confirm the variant parameter matches a variant the digest producer actually runs for (full/en is the primary; tech/en, energy/en may lag).","If persistent, check that the NLP digest worker (Railway) is running and writing the expected Redis keys."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// Before calling extract_entities/get_news_clusters, verify the digest is seeded\nconst health = await fetch('/api/health').then(r => r.json());\nconst digestKey = `news:digest:v1:${variant}:en`;\nif (!health.coverage?.[digestKey]) {\n  throw new Error(`Digest not seeded for ${variant}/en; retry after NLP cycle`);\n}","typeGuard":"function isMcpSourceUnavailableError(e: unknown): e is { unavailableInputs: string[]; failedInputs: string[] } & Error {\n  return e instanceof Error && (e as any).name === 'McpSourceUnavailableError';\n}","tryCatchPattern":"try {\n  const entities = await callMcpTool('extract_entities', { text: '...' });\n} catch (e) {\n  if (isMcpSourceUnavailableError(e) && e.unavailableInputs.includes(`news:digest:v1:${variant}:en`)) {\n    // Digest not ready — fall back to explicit text mode or retry\n    const fallback = await callMcpTool('extract_entities', { text: providedText });\n  } else throw e;\n}","preventionTips":["Confirm the NLP digest worker is running for the requested variant before relying on no-text mode.","Provide explicit text to extract_entities as a fallback when the digest is unavailable.","Monitor the news:digest:v1:*:en keys in GET /api/health coverage."],"tags":["mcp","redis","source-unavailable","nlp","news-digest"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}