santifer/career-ops · error · Error
telegram-channel: @ has no public preview — private…
Error message
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. What it means
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.
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
Example fix
// before channel: 'old_handle' // t.me 302 → t.me/renamed_handle // after channel: 'renamed_handle'
Defensive patterns
Strategy: fallback
Validate before calling
null
Type guard
null
Try / catch
try {
offers = await provider.fetch(entry, ctx);
} catch (e) {
if (e.message.includes('has no public preview')) {
console.warn(`${entry.name}: @${entry.channel} is private/preview-off — skipping or falling back to another source`);
return [];
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- [models] Failed to fetch free model list
- Access denied: Egress guard blocked private target IP
- Access denied: Egress guard blocked private target IP
- All active models failed. Last error
- APIFY_TOKEN not set
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/3ece408f00fc61b5.
Report an issue: GitHub.
Appendix: source
Thrown at providers/telegram-channel.mjs:392
const cutoff = Math.max(Date.now() - windowDays * DAY_MS, Number.isFinite(ctx.sinceMs) ? Number(ctx.sinceMs) : 0);
const label = entry.name || `@${channel}`;
const jobs = [];
let before = null;
let read = 0;
let reachedCutoff = false;
let page = 0;
for (; page < maxPages; page++) {
if (page > 0) await sleep(PAGE_DELAY_MS, ctx);
const url = `https://t.me/s/${channel}${before === null ? '' : `?before=${before}`}`;
let html;
try {
// redirect:'manual' is never followed, but the 3xx keeps its status and
// Location, so a private channel is a named failure, not "fetch failed".
html = await fetchTextWithRetry(ctx, url, { redirect: 'manual' });
} catch (err) {
if (page === 0 && err?.status >= 300 && err.status < 400) {
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.`);
}
throw err;
}
const { posts, noPreview, textPosts } = parseChannelPage(html, channel);
if (page === 0 && noPreview) {
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`);
}
if (page === 0 && posts.length === 0 && textPosts > 0) {
throw new Error(`telegram-channel: @${channel} served ${textPosts} text posts but none parsed — t.me markup changed`);
}
if (posts.length === 0) break;
const oldest = Math.min(...posts.map((p) => p.id));
if (before !== null && oldest >= before) break; // t.me re-served the same page
read += posts.length;
for (const post of posts) {
if (post.postedAt !== undefined && post.postedAt < cutoff) { reachedCutoff = true; continue; }
const job = postToJob(post);
if (job) jobs.push(job);View on GitHub (pinned to aac998c7ed)