koala73/worldmonitor · error · ConvexError

AUTH_REQUIRED

AUTH_REQUIRED

Error message

AUTH_REQUIRED

What it means

Thrown by the shared `requireUserId` helper in `convex/lib/auth.ts` when `resolveUserId` returns null — i.e. no Clerk identity AND (not in dev mode OR dev fallback disabled). This is the canonical auth gate for mutations/actions that always require a user. It is a plain-string ConvexError so `err.data === "AUTH_REQUIRED"`. It is deliberately a ConvexError (not a generic Error) so Convex's server-side Sentry integration treats it as an expected business error rather than reporting every unauthed query fire as an unhandled exception (WORLDMONITOR-N3). Unlike `followedCountries` which calls `getUserIdentity()` directly, this helper DOES honor the dev fallback (`DEV_USER_ID = "test-user-001"`) when `CONVEX_IS_DEV === "true"`.

Source

Thrown at convex/lib/auth.ts:63

  const identity = await ctx.auth.getUserIdentity();
  if (identity?.subject) return identity;
  return null;
}

/**
 * Returns the current user's ID or throws if unauthenticated.
 * Use for mutations/actions that always require auth.
 */
export async function requireUserId(
  ctx: QueryCtx | MutationCtx | ActionCtx,
): Promise<string> {
  const userId = await resolveUserId(ctx);
  if (!userId) {
    // Throw as ConvexError so Convex's server-side Sentry integration treats it
    // as an expected business error (WebSocket/auth races on query fire) rather
    // than reporting every unauthed query fire as an unhandled exception
    // (WORLDMONITOR-N3).
    throw new ConvexError("AUTH_REQUIRED");
  }
  return userId;
}

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Gate the calling UI on `useAuth().isSignedIn` before invoking the mutation.
  2. For local dev without Clerk, set `CONVEX_IS_DEV=true` in `.env.local` so `requireUserId` returns `DEV_USER_ID`.
  3. On `err.data === "AUTH_REQUIRED"`, redirect to sign-in and retry the action after re-auth.
  4. Never infer dev mode from a missing env var — explicitly set `CONVEX_IS_DEV=true`; otherwise production could silently fall back to the test user.

Example fix

// before (production, no session)
await convex.mutation(api.apiKeys.createApiKey, { name }); // uses requireUserId internally

// after — gate on auth, dev fallback for local
const { isSignedIn } = useAuth();
if (!isSignedIn) { navigate("/sign-in"); return; }
await convex.mutation(api.apiKeys.createApiKey, { name });

// .env.local for local dev without Clerk
// CONVEX_IS_DEV=true
Defensive patterns

Strategy: try-catch

Validate before calling

import { useAuth } from "@clerk/clerk-react";
const { isSignedIn } = useAuth();
if (!isSignedIn && process.env.CONVEX_IS_DEV !== "true") {
  navigate("/sign-in");
  return;
}

Type guard

// requireUserId honors DEV_USER_ID when CONVEX_IS_DEV=true;
// in production there is no fallback — auth is mandatory.

Try / catch

try {
  await convex.mutation(api.apiKeys.createApiKey, { name });
} catch (err) {
  if (err.data === "AUTH_REQUIRED") navigate("/sign-in");
  else throw err;
}

Prevention

When it happens

Trigger: Any mutation/action using `requireUserId` is called without a Clerk session; the session expired; in production (`CONVEX_IS_DEV` unset/false) with no auth header; calling from a context that doesn't propagate the Convex auth token.

Common situations: Session expired on a long-open tab; a mutation fired from a logged-out state; a background scheduler/action invoked without a user context; production deploy where the dev fallback correctly does NOT apply (by design — `isDev` is derived only from `CONVEX_IS_DEV`, never from a missing env var, so production never accidentally behaves as dev).

Related errors


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