{"record":{"id":"9330d07037dc7693","repo":"santifer/career-ops","slug":"telegram-channel-channel-has-no-public-preview-private-9330d0","errorCode":null,"errorMessage":"telegram-channel: @${channel} has no public preview (private channel, or preview switched off) — it cannot be read without an authenticated Telegram integration","messagePattern":"telegram-channel: @(.+?) has no public preview \\(private channel, or preview switched off\\) — it cannot be read without an authenticated Telegram integration","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/telegram-channel.mjs","lineNumber":398,"sourceCode":"    let reachedCutoff = false;\n    let page = 0;\n    for (; page < maxPages; page++) {\n      if (page > 0) await sleep(PAGE_DELAY_MS, ctx);\n      const url = `https://t.me/s/${channel}${before === null ? '' : `?before=${before}`}`;\n      let html;\n      try {\n        // redirect:'manual' is never followed, but the 3xx keeps its status and\n        // Location, so a private channel is a named failure, not \"fetch failed\".\n        html = await fetchTextWithRetry(ctx, url, { redirect: 'manual' });\n      } catch (err) {\n        if (page === 0 && err?.status >= 300 && err.status < 400) {\n          throw new Error(`telegram-channel: @${channel} has no public preview — private channel, preview switched off, or no such channel (t.me answered ${err.status}${err.location ? ` → ${err.location}` : ''}). It cannot be read without an authenticated Telegram integration.`);\n        }\n        throw err;\n      }\n      const { posts, noPreview, textPosts } = parseChannelPage(html, channel);\n      if (page === 0 && noPreview) {\n        throw new Error(`telegram-channel: @${channel} has no public preview (private channel, or preview switched off) — it cannot be read without an authenticated Telegram integration`);\n      }\n      if (page === 0 && posts.length === 0 && textPosts > 0) {\n        throw new Error(`telegram-channel: @${channel} served ${textPosts} text posts but none parsed — t.me markup changed`);\n      }\n      if (posts.length === 0) break;\n      const oldest = Math.min(...posts.map((p) => p.id));\n      if (before !== null && oldest >= before) break; // t.me re-served the same page\n      read += posts.length;\n      for (const post of posts) {\n        if (post.postedAt !== undefined && post.postedAt < cutoff) { reachedCutoff = true; continue; }\n        const job = postToJob(post);\n        if (job) jobs.push(job);\n      }\n      if (reachedCutoff) break;\n      before = oldest;\n    }\n    // Cap warning (same pattern as a16z-speedrun-talent/jibeapply/workday):\n    // the window was not reached, so the inventory is a prefix. Silent under","sourceCodeStart":380,"sourceCodeEnd":416,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/telegram-channel.mjs#L380-L416","documentation":"The telegram-channel provider fetches a channel's public web preview at t.me/s/<channel>. Telegram only serves that preview for public channels with previews enabled; when the first page's HTML contains no preview content, the provider throws because it cannot read the channel at all without an authenticated Telegram client (Bot/MTProto API). This is an unconditional capability failure, not a transient network problem.","triggerScenarios":"Calling fetch() for a @channel where the t.me/s page (page 0) parses zero posts and parseChannelPage() reports noPreview=true — i.e. the channel is private, the public preview has been disabled in channel settings, or the handle does not exist.","commonSituations":"Configuring portals.yml with a private or deleted Telegram channel; a channel admin turning off 'public preview' after the entry was added; a typo in the @handle pointing at a non-existent channel.","solutions":["Open https://t.me/s/<channel> in a browser; if no posts render, the channel genuinely has no public preview and this provider cannot read it.","Verify the @handle is spelled correctly and the channel exists (search it in the Telegram app).","If the channel is intentionally private, switch to an authenticated Telegram integration (Bot API or MTProto client such as GramJS) instead of the public-preview scraper.","Remove or disable the entry in portals.yml if the source is not readable publicly."],"exampleFix":"// before\n{ name: 'my-private-channel', provider: 'telegram-channel', channel: '@internals' }\n// after\n// channel must be public with preview enabled, e.g.\n{ name: 'my-channel', provider: 'telegram-channel', channel: '@publicjobs' }","handlingStrategy":"validation","validationCode":"// before configuring a telegram-channel entry, verify a public preview exists\nasync function hasPublicPreview(channel) {\n  const res = await fetch(`https://t.me/s/${channel.replace(/^@/, '')}`);\n  const html = await res.text();\n  return res.ok && html.includes('tgme_widget_message');\n}\n// only add the entry if hasPublicPreview('@channel') === true","typeGuard":"function isPublicTelegramChannel(entry) {\n  return typeof entry.channel === 'string' && /^@[A-Za-z0-9_]{4,}$/.test(entry.channel);\n}\n// plus a runtime preview check as above before enabling the entry","tryCatchPattern":"try {\n  await telegramProvider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).includes('no public preview')) {\n    console.warn(`Skipping ${entry.name}: not publicly readable — needs an authenticated Telegram integration`);\n    return null; // skip, don't fail the scan\n  }\n  throw err;\n}","preventionTips":["Check t.me/s/<channel> renders posts in a browser before adding any channel to portals.yml","Re-verify channel entries periodically — admins can disable previews at any time","Keep a list of private channels that require a Bot/MTProto integration and route them elsewhere","Double-check @handles for typos; a wrong handle usually means no preview"],"tags":["telegram","scraping","private-channel","configuration"],"backgroundTag":"authentication-required","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}