{"record":{"id":"83010dc54d89fc79","repo":"koala73/worldmonitor","slug":"discord-channel-must-be-set-via-set-discord-oauth","errorCode":null,"errorMessage":"discord channel must be set via set-discord-oauth","messagePattern":"discord channel must be set via set-discord-oauth","errorType":"validation","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/notificationChannels.ts","lineNumber":525,"sourceCode":"      }\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) {\n        await ctx.db.replace(existing._id, doc);\n      } else {\n        await ctx.db.insert(\"notificationChannels\", doc);\n      }\n    } else if (args.channelType === \"webhook\") {\n      if (!args.webhookEnvelope) throw new ConvexError(\"webhookEnvelope required for webhook channel\");\n      const doc = { userId, channelType: \"webhook\" as const, webhookEnvelope: args.webhookEnvelope, verified: true, linkedAt: now, webhookLabel: args.webhookLabel };\n      if (existing) {\n        await ctx.db.replace(existing._id, doc);\n      } else {\n        await ctx.db.insert(\"notificationChannels\", doc);\n      }\n    } else {\n      throw new ConvexError(\"discord channel must be set via set-discord-oauth\");\n    }\n  },\n});\n\nexport const deleteChannel = mutation({\n  args: { channelType: channelTypeValidator },\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();","sourceCodeStart":507,"sourceCodeEnd":543,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/notificationChannels.ts#L507-L543","documentation":"Thrown by the `setChannel` Convex mutation as the terminal `else` branch: `channelType` is neither `telegram`, `slack`, `email`, nor `webhook`. Discord channels are intentionally NOT supported here — they must be established through the dedicated `set-discord-oauth` OAuth flow, which stores Discord tokens rather than a static envelope. Plain string-typed ConvexError.","triggerScenarios":"Calling `setChannel({ channelType: \"discord\", ... })`. Any client that lets a user pick \"discord\" from the channel list and routes it into `setChannel` instead of the Discord OAuth handler.","commonSituations":"A unified channel-picker UI that sends every channel type to the same mutation; stale client code that predates the dedicated Discord OAuth flow; a typo/hardcoded value producing an unknown channelType.","solutions":["Route Discord setup through the `set-discord-oauth` flow instead of `setChannel`.","In the UI, branch on channelType: send `discord` to the OAuth entrypoint and everything else to `setChannel`.","Remove `discord` from the list of options that target `setChannel`."],"exampleFix":"// before\nawait setChannel({ channelType: \"discord\" });\n\n// after\n// Discord is linked via OAuth, not setChannel:\nwindow.location.href = discordOAuthStartUrl;","handlingStrategy":"validation","validationCode":"if (channelType === \"discord\") {\n  // Discord must go through OAuth, not setChannel\n  startDiscordOAuth();\n  return;\n}\nawait setChannel({ channelType, ...rest });","typeGuard":"const SETTABLE_CHANNELS = new Set([\"telegram\", \"slack\", \"email\", \"webhook\"]);\nfunction isSettableChannel(t: string): boolean {\n  return SETTABLE_CHANNELS.has(t);\n}","tryCatchPattern":null,"preventionTips":["Branch the UI so discord never reaches setChannel.","Remove discord from the options list that maps to setChannel."],"tags":["validation","convex","notifications","discord","oauth"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}