koala73/worldmonitor · critical

Redis request failed

Error message

Redis request failed

What it means

Thrown on the health snapshot read path when redisPipeline([['GET', snapshotKey]]) returns null. redisPipeline returns null on missing credentials, non-2xx HTTP, fetch timeout (4s here), or a response body that is not an array of the expected length. Because a failed snapshot read is treated as a real Redis outage rather than a cache miss, this triggers a 503 REDIS_DOWN.

Source

Thrown at api/health.js:2630

        : [],
      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([[
      'SET',
      HEALTH_VERDICT_REFRESH_LOCK_KEY,
      refreshLockToken,
      'EX',
      String(HEALTH_VERDICT_REFRESH_LOCK_TTL_SECONDS),
      'NX',

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Confirm the Upstash database is up and reachable via the Upstash console.
  2. Verify UPSTASH_REDIS_REST_TOKEN is current (a rotated token returns 401, which redisPipeline maps to null).
  3. Check Upstash latency/timeout metrics — if pipelines routinely approach 4s, the snapshot read needs a larger budget or the DB plan needs scaling.
  4. Retry the health request; transient nulls surface as a single 503 but the next snapshot read usually succeeds.

Example fix

// before
const snapshotResult = await redisPipeline([['GET', snapshotKey]], 4_000);
if (!snapshotResult) throw new Error('Redis request failed');
// after — surface the specific failure reason for diagnostics
// (handle in redisPipeline itself by returning a tagged failure rather than null)
Defensive patterns

Strategy: retry

Validate before calling

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

async function redisSnapshotReadable(): Promise<boolean> {
  if (!getRedisCredentials()) return false;
  const r = await redisPipeline([['PING']], 4_000);
  return r !== null && !r[0]?.error;
}

Try / catch

// health.js already wraps the snapshot read in a try/catch that returns 503
// REDIS_DOWN. Callers (UptimeRobot) should treat 503 as hard-down and alert.
// In application code that reuses redisPipeline:
try {
  const result = await redisPipeline([['GET', key]], 4_000);
  if (!result) throw new Error('Redis request failed');
  // ...
} catch (err) {
  // Distinguish credentials-missing (config fix) from null-on-outage (retry).
}

Prevention

When it happens

Trigger: GET /api/health (or ?compact=1) reaches api/health.js:2630 with credentials present (so error [0] did not fire) but the Upstash pipeline GET failed — Upstash returned 5xx, the fetch timed out at 4s, or the response body was malformed/not an array.

Common situations: Upstash regional outage or degraded latency pushing the pipeline over the 4s budget; an invalid/expired token causing a 401 (resp.ok false → null); a transient network blip between Vercel Edge and Upstash.

Related errors


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