{"record":{"id":"c7a6864c9c59f01b","repo":"koala73/worldmonitor","slug":"invalid-telegram-feed-response","errorCode":null,"errorMessage":"Invalid Telegram feed response","messagePattern":"Invalid Telegram feed response","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/telegram-intel.ts","lineNumber":189,"sourceCode":"  return {\n    id: parsed.id as string,\n    source: 'telegram',\n    channel: parsed.channel as string,\n    channelTitle: parsed.channelTitle as string,\n    url: parsed.url as string,\n    ts: parsed.ts as string,\n    text: parsed.text as string,\n    topic: parsed.topic as string,\n    tags: parsed.tags,\n    earlySignal: parsed.earlySignal !== false,\n    ...(Array.isArray(parsed.mediaUrls) ? { mediaUrls: parsed.mediaUrls.filter(value => typeof value === 'string') } : {}),\n    ...(parsed.watchlist === true ? { watchlist: true } : {}),\n  };\n}\n\nfunction parseTelegramFeedResponse(value: unknown): TelegramFeedResponse {\n  const parsed = asRecord(value);\n  if (!parsed || !Array.isArray(parsed.items)) throw new Error('Invalid Telegram feed response');\n  // Drop unparseable items rather than rejecting the whole payload. Throwing\n  // here turned one malformed post into a blanked panel: the caller falls back\n  // to a <=10min stale copy and then to the disabled empty state, discarding\n  // intel that was on screen a moment earlier.\n  const items = parsed.items\n    .map(parseTelegramItem)\n    .filter((item): item is TelegramItem => item !== null);\n  return {\n    source: typeof parsed.source === 'string' ? parsed.source : 'telegram',\n    earlySignal: parsed.earlySignal !== false,\n    // `!== false`, not `=== true`: an omitted `enabled` is shape drift, and\n    // reading it as \"relay disabled\" renders a permanent not-active state over\n    // a feed that is actually fine.\n    enabled: parsed.enabled !== false,\n    count: items.length,\n    updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : null,\n    items,\n  };","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/src/services/telegram-intel.ts#L171-L207","documentation":"parseTelegramFeedResponse requires the parsed body to be an object with an items array; anything else (null body, wrong shape, items not an array) throws 'Invalid Telegram feed response'. Notably, individual bad items inside a valid array are dropped rather than throwing — this error is reserved for the top-level envelope being wrong, because throwing on one malformed post previously blanked the whole panel.","triggerScenarios":"Calling fetchTelegramChannelFeed where the edge response body is not { items: [...] } — an HTML error page served with 200, a JSON error object without items, a truncated/empty body, or a contract change between client and deployed edge version.","commonSituations":"Edge function crash handlers returning { error } bodies with 200; CDN/proxy interception pages; deploying a new edge API while clients run an older schema; cache poisoning with non-feed payloads.","solutions":["Verify response.ok and that content-type is application/json before feeding the parser.","Deploy matching versions of the edge Telegram feed endpoint and the client parser; check for recent schema changes.","Catch the error and fall back to the stale cached feed (the service keeps a <=10min stale copy) or the disabled empty state.","Log the offending body to distinguish proxy interference from a real API contract break."],"exampleFix":"// before\nconst feed = await fetchTelegramChannelFeed(username); // throws, panel blanks\n// after\ntry {\n  const feed = await fetchTelegramChannelFeed(username);\n  renderFeed(feed.items);\n} catch {\n  renderFeed(lastKnownItems ?? []); // stale or empty-state fallback\n}","handlingStrategy":"fallback","validationCode":"const body = await res.json();\nif (body == null || typeof body !== 'object' || !Array.isArray((body as any).items)) {\n  throw new Error('Malformed feed envelope');\n}\n","typeGuard":"function isTelegramFeedResponse(v: unknown): v is TelegramFeedResponse {\n  return typeof v === 'object' && v !== null && Array.isArray((v as any).items);\n}\n","tryCatchPattern":"let feed: TelegramFeedResponse | null = null;\ntry {\n  feed = await fetchTelegramChannelFeed(handle);\n} catch {\n  feed = lastGoodFeed ?? null; // stale copy or null -> disabled empty state\n}\nrenderFeed(feed?.items ?? []);\n","preventionTips":["Keep a last-known-good feed and render it (marked stale) when a fetch fails","Verify response.ok and JSON content-type before parsing","Deploy client and edge schema changes together; smoke-test the feed envelope after edge deploys","Never let a single panel failure blank adjacent panels — scope the catch to the feed"],"tags":["validation","schema-mismatch","telegram","api-response"],"backgroundTag":"schema-validation-failed","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-09-01T10:32:37.851Z","contentChangedAt":"2026-09-01T10:32:37.851Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}