koala73/worldmonitor · critical · Error

Redis not configured

Error message

Redis not configured

What it means

Thrown at the top of the health snapshot read path when getRedisCredentials() returns null, meaning at least one of UPSTASH_REDIS_REST_URL or UPSTASH_REDIS_REST_TOKEN is unset. The health endpoint treats Redis as a hard dependency (it can assess nothing without it), so this surfaces as a 503 REDIS_DOWN response that UptimeRobot/k8s probes read as a hard failure. It fires before any Redis command is attempted.

Source

Thrown at api/health.js:2624

    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 ffec79ac33)

Solutions

  1. Set UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN in the Vercel project environment (or .env.local for local dev) and redeploy.
  2. Run `npm run worktree:env` (or worktree:bootstrap) in a fresh worktree to link the ignored .env.local that carries the Upstash credentials.
  3. Verify with `vercel env ls` (or the Vercel dashboard) that both keys exist for the target environment (Production/Preview/Development).
  4. Confirm the credentials point at a reachable Upstash REST endpoint by issuing one pipeline GET out-of-band.

Example fix

// before (env missing)
// UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN unset
//
// after (in .env.local / Vercel project env)
UPSTASH_REDIS_REST_URL=https://your-db.upstash.io
UPSTASH_REDIS_REST_TOKEN=AX...token
Defensive patterns

Strategy: validation

Validate before calling

import { getRedisCredentials } from './_upstash-json.js';

function assertRedisConfigured() {
  if (!getRedisCredentials()) {
    throw new Error('Missing UPSTASH_REDIS_REST_URL or UPSTASH_REDIS_REST_TOKEN');
  }
}
// Call at the start of any code path that depends on Redis.

Type guard

function redisConfigured(): boolean {
  return Boolean(process.env.UPSTASH_REDIS_REST_URL && process.env.UPSTASH_REDIS_REST_TOKEN);
}

Try / catch

try {
  if (!getRedisCredentials()) throw new Error('Redis not configured');
  // ... snapshot read
} catch (err) {
  // health.js already maps this to a 503 REDIS_DOWN; callers of redisPipeline
  // should distinguish missing-config (env fix) from outage (retry).
  if (err.message === 'Redis not configured') {
    // surface as a deployment-config incident, not a transient retry
  }
}

Prevention

When it happens

Trigger: A GET /api/health (or ?compact=1) request hits api/health.js:2624 on a Vercel Edge deployment where the Upstash Redis env vars were never set, were renamed, or were stripped from the preview/branch environment. Also triggered locally without a .env.local carrying the Upstash credentials.

Common situations: New preview/branch deploys that did not inherit the production Upstash env vars; a renamed or rotated env var after a secret rotation; running the endpoint in a fresh worktree that was not bootstrapped with `npm run worktree:env`; misconfigured Railway/Docker relay missing the same vars.

Related errors


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