{"record":{"id":"a071d12540763eae","repo":"koala73/worldmonitor","slug":"chatid-required-for-telegram-channel","errorCode":null,"errorMessage":"chatId required for telegram channel","messagePattern":"chatId required for telegram channel","errorType":"validation","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/notificationChannels.ts","lineNumber":493,"sourceCode":"    webhookLabel: v.optional(v.string()),\n  },\n  handler: async (ctx, args) => {\n    const identity = await ctx.auth.getUserIdentity();\n    if (!identity) throw new ConvexError(\"UNAUTHENTICATED\");\n    const userId = identity.subject;\n    await assertProEntitlement(ctx, userId);\n\n    const existing = await ctx.db\n      .query(\"notificationChannels\")\n      .withIndex(\"by_user_channel\", (q) =>\n        q.eq(\"userId\", userId).eq(\"channelType\", args.channelType),\n      )\n      .unique();\n\n    const now = Date.now();\n\n    if (args.channelType === \"telegram\") {\n      if (!args.chatId) throw new ConvexError(\"chatId required for telegram channel\");\n      const doc = { userId, channelType: \"telegram\" as const, chatId: args.chatId, verified: true, linkedAt: now };\n      if (existing) {\n        await ctx.db.replace(existing._id, doc);\n      } else {\n        await ctx.db.insert(\"notificationChannels\", doc);\n      }\n    } else if (args.channelType === \"slack\") {\n      if (!args.webhookEnvelope) throw new ConvexError(\"webhookEnvelope required for slack channel\");\n      const doc = { userId, channelType: \"slack\" as const, webhookEnvelope: args.webhookEnvelope, verified: true, linkedAt: now };\n      if (existing) {\n        await ctx.db.replace(existing._id, doc);\n      } else {\n        await ctx.db.insert(\"notificationChannels\", doc);\n      }\n    } else if (args.channelType === \"email\") {\n      if (!args.email) throw new ConvexError(\"email required for email channel\");\n      const doc = { userId, channelType: \"email\" as const, email: args.email, verified: true, linkedAt: now };\n      if (existing) {","sourceCodeStart":475,"sourceCodeEnd":511,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/notificationChannels.ts#L475-L511","documentation":"Thrown by `setChannel` when `channelType === \"telegram\"` but `args.chatId` is missing/empty. After auth and Pro-entitlement pass, the telegram branch requires a chatId to store. Plain-string ConvexError; `err.data === \"chatId required for telegram channel\"`. This is per-channel-type required-field validation — each branch (telegram/slack/email/webhook) has its own guard.","triggerScenarios":"Calling `setChannel({ channelType: \"telegram\" })` with no `chatId`; `chatId` is an empty string or undefined; the Telegram OAuth/link flow didn't yield a chat id before the mutation fired.","commonSituations":"The Telegram bot-link flow completed the auth step but the chat id wasn't captured; a UI form that submits the channel type before the chat id field is populated; a test calling the mutation with only the channel type.","solutions":["Capture the Telegram chat id from the bot-link callback before calling `setChannel`.","Client-side: disable the save button until `chatId` is non-empty for the telegram branch.","On `err.data === \"chatId required for telegram channel\"`, show a field-level error and re-run the bot-link flow."],"exampleFix":"// before\nawait convex.mutation(api.notificationChannels.setChannel, { channelType: \"telegram\" });\n\n// after\nif (channelType === \"telegram\" && !chatId) {\n  setFieldError(\"chatId\", \"Link your Telegram chat first.\");\n  return;\n}\nawait convex.mutation(api.notificationChannels.setChannel, { channelType: \"telegram\", chatId });","handlingStrategy":"validation","validationCode":"if (channelType === \"telegram\" && !chatId) {\n  setFieldError(\"chatId\", \"Link your Telegram chat first.\");\n  return;\n}","typeGuard":"function hasTelegramChatId(args: { channelType: string; chatId?: string }): boolean {\n  return args.channelType !== \"telegram\" || (typeof args.chatId === \"string\" && args.chatId.length > 0);\n}","tryCatchPattern":"try {\n  await convex.mutation(api.notificationChannels.setChannel, { channelType: \"telegram\", chatId });\n} catch (err) {\n  if (err.data === \"chatId required for telegram channel\") setFieldError(\"chatId\", \"Link your Telegram chat first.\");\n  else throw err;\n}","preventionTips":["Capture the Telegram chat id from the bot-link callback before calling setChannel.","Disable save until chatId is non-empty for the telegram branch.","Run the full bot-link flow if chatId is missing.","Plain-string error — branch on the exact message."],"tags":["validation","convex","notifications","telegram"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}