{"record":{"id":"3ece408f00fc61b5","repo":"santifer/career-ops","slug":"telegram-channel-channel-has-no-public-preview-private","errorCode":null,"errorMessage":"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.","messagePattern":"telegram-channel: @(.+?) has no public preview — private channel, preview switched off, or no such channel \\(t\\.me answered (.+?)(.+?)` : ''\\}\\)\\. It cannot be read without an authenticated Telegram integration\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/telegram-channel.mjs","lineNumber":392,"sourceCode":"    const cutoff = Math.max(Date.now() - windowDays * DAY_MS, Number.isFinite(ctx.sinceMs) ? Number(ctx.sinceMs) : 0);\n    const label = entry.name || `@${channel}`;\n\n    const jobs = [];\n    let before = null;\n    let read = 0;\n    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);","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/telegram-channel.mjs#L374-L410","documentation":"The provider requests t.me/s/<handle> with redirect:'manual' so a private (or nonexistent) channel's 3xx response is detected instead of silently followed. On the first page, any 3xx-400 response triggers this explicit message: the channel has no public preview and cannot be read without an authenticated Telegram integration.","triggerScenarios":"Channel is private; public preview (restrict saving / preview) is disabled in channel settings; the handle doesn't exist and t.me responds with a redirect; t.me returns 302 to a login or error location.","commonSituations":"Company moved to a private internal channel; channel renamed so old handle redirects; channel was always invite-only; typo pointing at a nonexistent handle that t.me redirects to an error page.","solutions":["Verify the handle exists and is public by opening https://t.me/s/<handle> in a browser","Enable public preview in the channel settings, or use a different public channel/mirror","If the channel must stay private, integrate an authenticated Telegram client (MTProto/Bot API) instead of this provider","Fix typos — a wrong handle often manifests as this redirect"],"exampleFix":"// before\nchannel: 'old_handle'  // t.me 302 → t.me/renamed_handle\n// after\nchannel: 'renamed_handle'","handlingStrategy":"fallback","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  offers = await provider.fetch(entry, ctx);\n} catch (e) {\n  if (e.message.includes('has no public preview')) {\n    console.warn(`${entry.name}: @${entry.channel} is private/preview-off — skipping or falling back to another source`);\n    return [];\n  }\n  throw e;\n}","preventionTips":["Check https://t.me/s/<handle> renders publicly before configuring a channel","Re-verify handles when a channel is renamed or goes private","Prefer public channels or set up an authenticated Telegram integration for private ones","Read the status/Location in the message — it distinguishes 'no such channel' from a redirect"],"tags":["telegram","network","auth","untrusted-external"],"backgroundTag":"http-error-response","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"}