koala73/worldmonitor · warning

REDIS_DOWN

REDIS_DOWN

Error message

Redis not configured

What it means

listApiKeys() treats a null Clerk user or null Convex client as an empty list (no throw). It throws only when waitForConvexAuthForUser(userId) is false while assertAccountStillCurrent(userId) passes, i.e., the user is still signed in as the same person but the Convex auth token never became ready within the 10s barrier timeout. This distinguishes a transient token-propagation failure from a real account switch, so the UI can show 'unknown' instead of a false 'no keys'.

Source

Thrown at api/health.js:3006

    const lastFailureRaw = results?.[0]?.result;
    const failureLogRaw = results?.[1]?.result;
    const body = {
      lastFailure: parseJson(lastFailureRaw),
      failureLog: Array.isArray(failureLogRaw)
        ? failureLogRaw.map(parseJson).filter((e) => e !== null)
        : [],
      checkedAt: new Date().toISOString(),
    };
    return new Response(JSON.stringify(body, null, 2), { status: 200, headers });
  }

  // A snapshot hit is one Redis command instead of the ~390-command registry
  // sweep below. A failed snapshot read is a real Redis outage, not a cache
  // miss: returning 503 preserves UptimeRobot's hard-down signal.
  let refreshLockToken = null;
  let ownsSnapshotRefreshLock = false;
  try {
    if (!getRedisCredentials()) throw new Error('Redis not configured');
    // Read the snapshot this request will actually render. `?compact=1` — the
    // browser poll, ~115k/day — reads the ~1 KB compact key instead of dragging the
    // full ~20 KB check map out of Redis to show a tenth of it (#5300).
    const snapshotKey = compact ? HEALTH_VERDICT_COMPACT_SNAPSHOT_KEY : HEALTH_VERDICT_SNAPSHOT_KEY;
    const snapshotResult = await redisPipeline([['GET', snapshotKey]], 4_000);
    if (!snapshotResult) throw new Error('Redis request failed');
    if (snapshotResult[0]?.error) throw new Error('Redis snapshot read failed');
    const cachedSnapshot = parseHealthVerdictSnapshot(snapshotResult[0]?.result, snapshotNow(), { requireChecks: !compact });
    // Activation deadlines are exact to the second, so the 60s verdict cache
    // must not outlive either rollout grace. A snapshot written just before a
    // deadline would otherwise keep serving a softened verdict for up to a
    // minute after strictness was supposed to begin. Sweep fresh instead.
    if (cachedSnapshot && !hasExpiredActivationGrace(cachedSnapshot, snapshotNow())) {
      return healthResponse(cachedSnapshot, compact, headers);
    }

    refreshLockToken = `${now}:${crypto.randomUUID()}`;
    let lockResult = await redisPipeline([[

View on GitHub (pinned to eeab0a219f)

Solutions

  1. Retry the load after a short delay; token propagation usually completes on the next attempt
  2. Verify network access to both Clerk and the Convex deployment (no blocked requests in the Network tab)
  3. Reload the page to force a fresh auth rebind if it persists
  4. Render the key list as 'unavailable, retry' rather than empty when this error fires

Example fix

// before
const keys = await listApiKeys(); // can throw 'Authentication unavailable while loading API keys.'

// after: one bounded retry, then an explicit unavailable state
let keys: ApiKeyInfo[] | null = null;
for (let attempt = 0; attempt < 2 && !keys; attempt++) {
  try { keys = await listApiKeys(); }
  catch (e) {
    if (attempt === 1 || !(e instanceof Error && e.message.includes('Authentication unavailable'))) throw e;
    await new Promise(r => setTimeout(r, 1500));
  }
}
renderKeys(keys ?? 'unavailable');
Defensive patterns

Strategy: retry

Validate before calling

const userId = getCurrentClerkUser()?.id;
if (!userId) { renderKeys([]); return; } // signed out is genuinely empty
if (!(await getConvexClient())) { renderKeys([]); return; }

Type guard

const isAuthUnavailableError = (e: unknown): e is Error =>
  e instanceof Error && e.message.startsWith('Authentication unavailable');

Try / catch

try {
  const keys = await listApiKeys();
  renderKeys(keys);
} catch (e) {
  if (isAuthUnavailableError(e)) renderKeys('unavailable'); // NOT empty — truth is unknown
  else throw e;
}

Prevention

When it happens

Trigger: Clerk session valid but the Convex auth rebind (startConvexAuthRebind/installConvexAuth) never delivers a server-confirmed token within 10s; slow or blocked token fetch; WebSocket pause preventing the barrier from completing.

Common situations: Slow networks on first load; ad-blockers blocking Clerk or Convex endpoints; a backgrounded tab with throttled timers; a brief Convex deployment hiccup.

Related errors


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/87240ae15d89706b. Report an issue: GitHub.