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

Routing guard in setChannel: channelType is 'discord', which deliberately has no direct payload path — Discord channels must be created through the separate set-discord-oauth flow because they require an OAuth grant (bot/guild binding), not pasted credentials.

Solutions

  1. Use the set-discord-oauth mutation or flow to link a Discord channel
  2. Hide Discord from the generic setChannel UI and route users to the OAuth flow directly
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at convex/notificationChannels.ts:525 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at convex/notificationChannels.ts:558

      }
    } else if (args.channelType === "email") {
      const email = requireVerifiedAccountEmail(args.email, identity.emailVerified === true ? identity.email : undefined);
      const doc = { userId, channelType: "email" as const, email, emailOwnership: "verified_account" as const, 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");
    }
  },
});

// Called by the notification relay via /relay/deactivate HTTP action
// when Telegram returns 403 or Slack returns 404/410. Ungated by design —
// delivery-side deactivation must work after Pro lapses (see #8430).
export const deactivateChannelForUser = internalMutation({
  args: { userId: v.string(), channelType: channelTypeValidator },
  handler: async (ctx, args) => {
    await deactivateChannelRow(ctx, args.userId, args.channelType);
  },
});

// INTERNAL ONLY — #8430. Formerly public `mutation`s with a Pro gate that
// contradicted the ungated relay path. Kept as identity-scoped internals so
// authenticated deploy-key callers and Sentry probe filters still resolve the
// historical names; production traffic uses `*ForUser` above.

View on GitHub (pinned to e586b8b4b8)