koala73/worldmonitor · error · ConvexError
telegram channel must be linked through bot pairing
telegram channel must be linked through bot pairing
Error message
telegram channel must be linked through bot pairing
What it means
The notification-channel linking mutation rejects channelType "telegram" outright: Telegram channels cannot be created by supplying a webhook/token like Slack or email. They may only be established through the bot-pairing flow (user links via the bot), so this code path treats any telegram insert/update request as unsupported.
Solutions
- Use the Telegram bot pairing flow (/start with the pairing code) instead of this mutation.
- Remove 'telegram' from client-side channel-type options for this direct-linking path.
- If a Telegram channel must be created programmatically, route through the bot-pairing internal mutation that owns that flow.
Example fix
// before
await api.notificationChannels.link({ channelType: "telegram", telegramChatId: "123" });
// after
// pair via the bot, then use the pairing endpoint
await api.notificationChannels.completeBotPairing({ pairingCode: code }); Defensive patterns
Strategy: validation
Validate before calling
if (channelType === "telegram") {
throw new Error("use the Telegram bot pairing flow; direct linking is not supported");
} Try / catch
try {
await linkChannel({ channelType });
} catch (e) {
if (String(e?.message).includes("bot pairing")) {
startTelegramBotPairing();
}
} Prevention
- Remove 'telegram' from direct channel-linking UI options.
- Route telegram connections exclusively through the bot pairing flow.
- Add a client-side enum of linkable channel types (slack, email) shared with the backend.
When it happens
Trigger: Calling the link-notification-channel mutation with channelType "telegram" and expecting it to create or update a notificationChannels document directly.
Common situations: Client UI or script migrated from a generic channel-linking form to Telegram; developer tries to seed a Telegram channel server-side; a stale client still offers 'Telegram' in its channel-type dropdown.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/732c66db2d95d626.
Report an issue: GitHub.
Appendix: source
Thrown at convex/notificationChannels.ts:224
email: v.optional(v.string()),
webhookLabel: v.optional(v.string()),
scheduleWelcome: v.optional(v.boolean()),
// Internal-only: derived by the relay HTTP handler from Clerk, never the body.
verifiedAccountEmail: v.optional(v.string()),
},
handler: async (ctx, args) => {
const { userId, channelType, webhookEnvelope, email, webhookLabel } = args;
const existing = await ctx.db
.query("notificationChannels")
.withIndex("by_user_channel", (q) =>
q.eq("userId", userId).eq("channelType", channelType),
)
.unique();
const isNew = !existing;
let channelId = existing ? String(existing._id) : "";
const now = Date.now();
if (channelType === "telegram") {
throw new ConvexError("telegram channel must be linked through bot pairing");
} else if (channelType === "slack") {
if (!webhookEnvelope) throw new ConvexError("webhookEnvelope required for slack channel");
const doc = { userId, channelType: "slack" as const, webhookEnvelope, verified: true, linkedAt: now };
if (existing) { await ctx.db.replace(existing._id, doc); } else { channelId = String(await ctx.db.insert("notificationChannels", doc)); }
} else if (channelType === "email") {
await assertProEntitlement(ctx, userId);
const recipient = requireVerifiedAccountEmail(email, args.verifiedAccountEmail);
const doc = { userId, channelType: "email" as const, email: recipient, emailOwnership: "verified_account" as const, verified: true, linkedAt: now };
if (existing) { await ctx.db.replace(existing._id, doc); } else { channelId = String(await ctx.db.insert("notificationChannels", doc)); }
} else if (channelType === "webhook") {
if (!webhookEnvelope) throw new ConvexError("webhookEnvelope required for webhook channel");
const doc = { userId, channelType: "webhook" as const, webhookEnvelope, verified: true, linkedAt: now, webhookLabel };
if (existing) { await ctx.db.replace(existing._id, doc); } else { channelId = String(await ctx.db.insert("notificationChannels", doc)); }
} else {
throw new ConvexError("discord channel must be set via set-discord-oauth");
}
if (isNew && args.scheduleWelcome === true) {
await ctx.scheduler.runAfter(View on GitHub (pinned to 7d06c8633d)