koala73/worldmonitor · error · ConvexError

INPUT_TOO_LARGE

INPUT_TOO_LARGE

Error message

INPUT_TOO_LARGE

What it means

Thrown by `mergeAnonymousLocal` (Step 3) when `args.countries.length > MAX_MERGE_INPUT`. This is a defensive upper-bound to prevent an enormous payload from a tampered client or a corrupted local-store from doing unbounded work in the ISO2-filter loop and subsequent per-country inserts. Carries `{ kind: "INPUT_TOO_LARGE", max: MAX_MERGE_INPUT, received }`. Distinct from INVALID_COUNTRY (per-code) and EMPTY_INPUT.

Source

Thrown at convex/followedCountries.ts:502

 * 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 {
        droppedInvalid.push(code);
      }
    }

    // Step 5: canonicalize — dedupe in first-seen order. Without this, a

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Client-side: cap the local follows array before calling; `countries.slice(0, MAX_MERGE_INPUT)`.
  2. On `err.data.kind === "INPUT_TOO_LARGE"`, truncate and retry, or investigate why local state exceeds the bound (likely a bug).
  3. If the legitimate use case exceeds the cap, raise `MAX_MERGE_INPUT` in the mutation — do not silently drop.

Example fix

// before
await convex.mutation(api.followedCountries.mergeAnonymousLocal, { countries: allFollows });

// after
const MAX = 100; // keep in sync with server MAX_MERGE_INPUT
const capped = allFollows.slice(0, MAX);
if (allFollows.length > MAX) console.warn("truncating merge input", allFollows.length);
await convex.mutation(api.followedCountries.mergeAnonymousLocal, { countries: capped });
Defensive patterns

Strategy: validation

Validate before calling

const MAX_MERGE_INPUT = 100; // keep in sync with server
const capped = localFollows.slice(0, MAX_MERGE_INPUT);
if (localFollows.length > MAX_MERGE_INPUT) {
  console.warn("merge input exceeds cap; truncating", localFollows.length);
}
if (capped.length === 0) return;

Type guard

function withinMergeCap(countries: string[]): boolean {
  return countries.length > 0 && countries.length <= MAX_MERGE_INPUT;
}

Try / catch

try {
  await convex.mutation(api.followedCountries.mergeAnonymousLocal, { countries: capped });
} catch (err) {
  if (err.data?.kind === "INPUT_TOO_LARGE") {
    await convex.mutation(api.followedCountries.mergeAnonymousLocal, { countries: localFollows.slice(0, err.data.max) });
  } else throw err;
}

Prevention

When it happens

Trigger: Calling the mutation with an array larger than `MAX_MERGE_INPUT`; a corrupted/anonymized local-store holding thousands of entries; a client that accidentally passes a paginated/global country list instead of the user's follows.

Common situations: A bug where the entire ISO country registry (~250 codes) is passed instead of the user's selections; a test fixture with a huge array; a tampered client attempting to flood the merge.

Related errors


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