koala73/worldmonitor · error · ConvexError

EMPTY_INPUT

EMPTY_INPUT

Error message

EMPTY_INPUT

What it means

Guard in mergeAnonymousLocal: args.countries is an empty array, so there is nothing to merge from the anonymous local state. Thrown as a typed ConvexError so callers can skip the merge instead of performing a pointless authenticated mutation.

Solutions

  1. Skip calling mergeAnonymousLocal when the local countries list is empty
  2. Catch EMPTY_INPUT client-side and treat it as a no-op success
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at convex/followedCountries.ts:497 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at convex/followedCountries.ts:497

 *
 * Resolves Codex-deepening round-1 P0 (server-side cap on merge) and
 * round-2 P1 (canonicalize duplicates before counting). Free users with
 * existingCount >= LIMIT accept zero new rows — never silently grow above
 * the cap during merge. (Grandfathering above-cap rows on PRO→free
 * downgrade is a separate concern handled by NOT auto-deleting on
 * downgrade; merge is the FIRST sign-in and has no PRO history to
 * grandfather.)
 */
export const mergeAnonymousLocal = mutation({
  args: { countries: v.array(v.string()) },
  handler: async (ctx, args): Promise<MergeAnonymousLocalResult> => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new ConvexError({ kind: "UNAUTHENTICATED" });
    const userId = identity.subject;

    // Step 2: empty-input guard.
    if (args.countries.length === 0) {
      throw new ConvexError({ kind: "EMPTY_INPUT" });
    }

    // Step 3: defensive upper-bound on input length.
    if (args.countries.length > MAX_MERGE_INPUT) {
      throw new ConvexError({
        kind: "INPUT_TOO_LARGE",
        max: MAX_MERGE_INPUT,
        received: args.countries.length,
      });
    }

    // Step 4: ISO-2 registry filter; collect droppedInvalid in input order.
    const droppedInvalid: string[] = [];
    const validInputs: string[] = [];
    for (const code of args.countries) {
      if (isValidIso2(code)) {
        validInputs.push(code);
      } else {

View on GitHub (pinned to eeab0a219f)