koala73/worldmonitor · warning · ConvexError
EMPTY_INPUT
EMPTY_INPUT
Error message
EMPTY_INPUT
What it means
Thrown by `mergeAnonymousLocal` (Step 2) when `args.countries.length === 0`. This is a guard before the upper-bound and ISO2-filter steps. Merging zero countries is a no-op and indicates the client dispatched the merge with no local state. Carries `{ kind: "EMPTY_INPUT" }`. It is distinct from `INPUT_TOO_LARGE` (too many) and from a successful merge that drops all-invalid codes (which returns `droppedInvalid` instead of throwing).
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 ffec79ac33)
Solutions
- Client-side: only call `mergeAnonymousLocal` when `localCountries.length > 0`.
- On `err.data.kind === "EMPTY_INPUT"`, treat as a no-op success (clear any pending-merge flag) — there is nothing to merge.
- Initialize local follows from localStorage with a default of `[]` and skip the mutation when empty.
Example fix
// before
await convex.mutation(api.followedCountries.mergeAnonymousLocal, { countries: localFollows ?? [] });
// after
const local = JSON.parse(localStorage.getItem("anonFollows") ?? "[]");
if (local.length === 0) { localStorage.removeItem("pendingMerge"); return; }
await convex.mutation(api.followedCountries.mergeAnonymousLocal, { countries: local }); Defensive patterns
Strategy: validation
Validate before calling
const local: string[] = JSON.parse(localStorage.getItem("anonFollows") ?? "[]");
if (!Array.isArray(local) || local.length === 0) {
localStorage.removeItem("pendingMerge");
return; // nothing to merge
} Type guard
function hasMergeInput(countries: unknown): countries is string[] {
return Array.isArray(countries) && countries.length > 0;
} Try / catch
try {
await convex.mutation(api.followedCountries.mergeAnonymousLocal, { countries: local });
} catch (err) {
if (err.data?.kind === "EMPTY_INPUT") { /* no-op success */ localStorage.removeItem("pendingMerge"); }
else throw err;
} Prevention
- Skip the mutation when local follows is empty.
- Treat EMPTY_INPUT as a successful no-op.
- Clear the pending-merge flag on EMPTY_INPUT.
- Default localStorage parse to [].
When it happens
Trigger: Calling `mergeAnonymousLocal` with `countries: []`; the local-storage key for anonymous follows is missing/empty and the client unconditionally dispatches the merge; a first sign-in for a user who never followed anything anonymously.
Common situations: The anonymous-follows localStorage is cleared (different browser, incognito) and the merge fires anyway; the merge is wired to a sign-in event without checking whether local follows exist; a migration path that always calls merge "just in case".
Related errors
- INPUT_TOO_LARGE
- INCOMPATIBLE_DELIVERY
- COUNTRIES_LIMIT_EXCEEDED
- TICKERS_LIMIT_EXCEEDED
- digestHour must be an integer 0–23
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/ff1f42b9493b25d4.
Report an issue: GitHub.