koala73/worldmonitor · error · ConvexError

UNAUTHENTICATED

UNAUTHENTICATED

Error message

UNAUTHENTICATED

What it means

`setPreferences` requires a Clerk identity via `ctx.auth.getUserIdentity()`. A null identity means there is no authenticated session, and the mutation throws a structured ConvexError `{ kind: "UNAUTHENTICATED" }` so the edge handler routes on `err.data.kind` and Sentry captures the drift. This is an auth-failure fast-fail, not a normal control-flow path.

Source

Thrown at convex/userPreferences.ts:164

  return { ok: true };
}

export const setPreferences = mutation({
  args: {
    variant: v.string(),
    data: v.any(),
    expectedSyncVersion: v.number(),
    schemaVersion: v.optional(v.number()),
  },
  handler: async (ctx, args): Promise<SetPreferencesResult> => {
    const identity = await ctx.auth.getUserIdentity();
    // UNAUTHENTICATED throws as a structured ConvexError because it is rare
    // auth drift / bad input we want surfaced in Sentry. Convex's
    // wire format propagates `errorData` for object payloads so the edge
    // handler routes via `err.data.kind`. (PR #3466 fixed the original
    // string-data wire-strip bug.)
    if (!identity) throw new ConvexError({ kind: "UNAUTHENTICATED" });
    const userId = identity.subject;

    // Run before the CAS read so stale expectedSyncVersion requests cannot
    // bypass the authoritative direct-Convex backstop by intentionally
    // returning CONFLICT forever. CONFLICT retries count as write attempts;
    // the limit is sized for that worst-case retry profile.
    const rateLimit = await checkUserPrefsWriteRateLimit(ctx, userId);
    if (!rateLimit.ok) return rateLimit;

    const existing = await ctx.db
      .query("userPreferences")
      .withIndex("by_user_variant", (q) =>
        q.eq("userId", userId).eq("variant", args.variant),
      )
      .unique();

    const data = preserveOmittedRollingDeploymentFields(existing?.data, args.data);
    const blobSize = JSON.stringify(data).length;

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Ensure the user is signed in before invoking setPreferences
  2. Re-authenticate (refresh the Clerk session) and retry
  3. Verify Clerk environment/keys are correct and the session cookie is present
Defensive patterns

Strategy: validation

Validate before calling

// Only call setPreferences when authenticated.
if (!clerk.user) { /* prompt sign-in; do not call */ }
else { await convex.mutation(api.userPreferences.setPreferences, args); }

Type guard

function isAuthenticated(user: { id: string | null } | null): user is { id: string } {
  return Boolean(user && user.id);
}

Try / catch

try {
  await convex.mutation(api.userPreferences.setPreferences, args);
} catch (err) {
  if (err.data?.kind === 'UNAUTHENTICATED') {
    // re-authenticate and retry; this is rare auth drift worth surfacing
  } else { throw err; }
}

Prevention

When it happens

Trigger: Calling `setPreferences` without a valid Clerk session: expired session token, signed-out client still making calls, or a cookie/token issue.

Common situations: Session expired while the SPA was idle; client fired the mutation after sign-out; auth cookie blocked by browser settings; Clerk keys misconfigured.

Understand the failure class

Related errors


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