{"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":321,"sourceCode":"        .slice(0, 12),\n      stale: cov.state === 'stale',\n      // #7084: complete the stale disclosure. The flag alone says the\n      // evidence is old without saying how old or why — an agent deciding\n      // whether stale evidence is usable needs the age.\n      staleAgeSeconds: cov.state === 'stale'\n        ? nlpClampInt(cov.staleAgeSeconds ?? 0, 0, Number.MAX_SAFE_INTEGER, 0)\n        : 0,\n      staleReason: cov.state === 'stale'\n        ? nlpTruncateUtf8(cov.staleReason ?? '', NLP_DIGEST_METADATA_MAX_BYTES)\n        : '',\n    }\n    : undefined;\n  if (\n    Object.keys(categories).length === 0\n    && Object.keys(body.feedStatuses ?? {}).length === 0\n    && digestCoverage?.state !== 'unavailable'\n  ) {\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 if (digestCoverage?.state === 'unavailable') {\n    // This is a valid empty response, not an unknown category. Preserve the\n    // requested filter and the explicit coverage state without a misleading\n    // corrective note.\n    groups = [];","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/api/mcp/registry/nlp-tools.ts#L303-L339","documentation":"McpSourceUnavailableError thrown in list-feed-digest (api/mcp/registry/nlp-tools.ts:195): the digest endpoint fetch itself succeeded (assertToolFetchOk passed) but the response body has zero categories AND zero feedStatuses — structurally an empty shell. The error names the exact cache key ('news:digest:v1:<variant>:en') in unavailable_inputs so the caller knows which producer is missing, with an empty failed_inputs list (this is a genuine miss, not a Redis read error).","triggerScenarios":"Calling list-feed-digest for a variant whose digest producer has not published yet — e.g. a newly rolled-out language variant where the news digest cron has not run, or a deployment where the digest seed was skipped. The endpoint returns 200 with an empty object.","commonSituations":"New variant deployment ordering (MCP tool shipped before the variant's digest producer). Digest generation job failed silently upstream. Digest key evicted/TTL-expired so the endpoint serves an empty shell. Local dev against prod API without the local digest seeded.","solutions":["Retry later — the digest producer runs on a cadence and the shell becomes populated after the next run","Check /api/health for the news:digest:v1:<variant>:en key freshness","Hit the underlying digest API endpoint directly to confirm it also returns the empty shell (isolates MCP from the producer)","If the variant should already be live, run the news digest seed script or restart its producer"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-check the digest key for the variant via health before calling\nconst health = await fetch('https://<host>/api/health').then(r => r.json());\nif (!health?.datasets?.[`news:digest:v1:${variant}:en`]?.fresh) defer('digest not published yet');","typeGuard":"function isSourceUnavailable(e) {\n  return e?.name === 'McpSourceUnavailableError'\n    || (typeof e?.message === 'string' && e.message.startsWith('Feed digest unavailable for '));\n}","tryCatchPattern":"try {\n  return await client.callTool('list-feed-digest', { variant });\n} catch (e) {\n  if (isSourceUnavailable(e)) {\n    // unavailable_inputs names the exact key: news:digest:v1:<variant>:en\n    return retryAfter(digestProducerCadenceMs);\n  }\n  throw e;\n}","preventionTips":["For a newly deployed variant, verify its digest producer ran once before advertising the tool","The error names the exact cache key — log it and alert per-variant rather than globally","Distinguish this from an HTTP failure: the endpoint returned 200; only the content was empty"],"tags":["mcp","nlp","digest","news","data-source"],"backgroundTag":"data-source-unavailable","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-08-27T19:53:08.521Z","contentChangedAt":"2026-08-27T19:53:08.521Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}