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
- Ensure the user is signed in before invoking setPreferences
- Re-authenticate (refresh the Clerk session) and retry
- 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
- Gate preference writes behind a signed-in check
- Refresh the Clerk session on long-idle SPAs before writing
- Verify Clerk env keys are correct so sessions persist
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/117440308d766955.
Report an issue: GitHub.