koala73/worldmonitor · error · ConvexError

userId is required

Error message

userId is required

What it means

The internal checkout mutation requires an explicit userId argument because it runs without a browser identity (internal functions have no auth context). An empty/missing args.userId makes the target user unattributable, so the mutation throws before validating the product.

Solutions

  1. Resolve and pass the Convex user's _id as userId before calling the mutation.
  2. Fix the upstream lookup (email -> user mapping) that produced an undefined userId.
  3. Skip checkout creation for requests with no attributable user instead of calling the mutation with a blank id.

Example fix

// before
await ctx.runMutation(api.payments.checkout.internalCreateCheckout, { productId } as any);
// after
if (!user?._id) throw new Error("cannot checkout without user");
await ctx.runMutation(api.payments.checkout.internalCreateCheckout, { userId: user._id, productId });
Defensive patterns

Strategy: validation

Validate before calling

if (!userId) throw new Error("internalCreateCheckout requires a resolved userId");
await runMutation(api.payments.checkout.internalCreateCheckout, { userId, productId });

Type guard

function hasUserId(u: { _id?: string } | null | undefined): u is { _id: string } {
  return typeof u?._id === "string" && u._id.length > 0;
}

Try / catch

try {
  await internalCreateCheckout({ userId, productId });
} catch (e) {
  if (String(e?.message) === "userId is required") {
    logUnattributableCheckoutRequest(context); // do not retry without a user
  }
}

Prevention

When it happens

Trigger: Calling the internal checkout mutation (the one with bypassPendingGuard in its args at checkout.ts:494) with userId undefined, null, or an empty string — e.g. from a webhook/cron where the user lookup failed.

Common situations: Webhook payload lacked the user reference used to resolve userId; caller code passes a variable that is undefined after a failed users-table lookup; cron job scheduling checkouts without resolving a user; omitting the field entirely from the function args object.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/4931f109c08e1e60. Report an issue: GitHub.

Appendix: source

Thrown at convex/payments/checkout.ts:494

// Internal action: called by /relay/create-checkout with trusted userId
// ---------------------------------------------------------------------------

export const internalCreateCheckout = internalAction({
  args: {
    userId: v.string(),
    email: v.optional(v.string()),
    name: v.optional(v.string()),
    productId: v.string(),
    returnUrl: v.optional(v.string()),
    discountCode: v.optional(v.string()),
    referralCode: v.optional(v.string()),
    attributionSource: v.optional(v.string()),
    // See createCheckout — skips only the pending-payment guard (#4438).
    bypassPendingGuard: v.optional(v.boolean()),
  },
  handler: async (ctx, args) => {
    if (!args.userId) {
      throw new ConvexError("userId is required");
    }
    requireCheckoutProduct(args.productId);
    if (args.bypassPendingGuard) {
      // See createCheckout — audit the pending-guard bypass (#4438 review).
      console.info(`[checkout] pending-payment guard bypassed user=${args.userId} product=${args.productId}`);
    }
    // Both guards concurrently (no shared data); subscription block still wins,
    // bypass skips the pending query (#4438 review).
    const [blocking, pending] = await Promise.all([
      getCheckoutBlockingSubscription(ctx, args.userId, args.productId),
      args.bypassPendingGuard
        ? Promise.resolve(null)
        : getCheckoutBlockingPendingPayment(ctx, args.userId, args.productId),
    ]);
    if (blocking) {
      return buildBlockedCheckoutResponse(blocking);
    }
    if (pending) {

View on GitHub (pinned to 7d06c8633d)