koala73/worldmonitor · error · ConvexError

discord channel must be set via set-discord-oauth

Error message

discord channel must be set via set-discord-oauth

What it means

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.

Source

Thrown at convex/notificationChannels.ts:525

      }
    } else if (args.channelType === "email") {
      if (!args.email) throw new ConvexError("email required for email channel");
      const doc = { userId, channelType: "email" as const, email: args.email, verified: true, linkedAt: now };
      if (existing) {
        await ctx.db.replace(existing._id, doc);
      } else {
        await ctx.db.insert("notificationChannels", doc);
      }
    } else if (args.channelType === "webhook") {
      if (!args.webhookEnvelope) throw new ConvexError("webhookEnvelope required for webhook channel");
      const doc = { userId, channelType: "webhook" as const, webhookEnvelope: args.webhookEnvelope, verified: true, linkedAt: now, webhookLabel: args.webhookLabel };
      if (existing) {
        await ctx.db.replace(existing._id, doc);
      } else {
        await ctx.db.insert("notificationChannels", doc);
      }
    } else {
      throw new ConvexError("discord channel must be set via set-discord-oauth");
    }
  },
});

export const deleteChannel = mutation({
  args: { channelType: channelTypeValidator },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new ConvexError("UNAUTHENTICATED");
    const userId = identity.subject;
    await assertProEntitlement(ctx, userId);

    const existing = await ctx.db
      .query("notificationChannels")
      .withIndex("by_user_channel", (q) =>
        q.eq("userId", userId).eq("channelType", args.channelType),
      )
      .unique();

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Route Discord setup through the `set-discord-oauth` flow instead of `setChannel`.
  2. In the UI, branch on channelType: send `discord` to the OAuth entrypoint and everything else to `setChannel`.
  3. Remove `discord` from the list of options that target `setChannel`.

Example fix

// before
await setChannel({ channelType: "discord" });

// after
// Discord is linked via OAuth, not setChannel:
window.location.href = discordOAuthStartUrl;
Defensive patterns

Strategy: validation

Validate before calling

if (channelType === "discord") {
  // Discord must go through OAuth, not setChannel
  startDiscordOAuth();
  return;
}
await setChannel({ channelType, ...rest });

Type guard

const SETTABLE_CHANNELS = new Set(["telegram", "slack", "email", "webhook"]);
function isSettableChannel(t: string): boolean {
  return SETTABLE_CHANNELS.has(t);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/83010dc54d89fc79. Report an issue: GitHub.