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
- Confirm the Upstash database is up and reachable via the Upstash console.
- Verify UPSTASH_REDIS_REST_TOKEN is current (a rotated token returns 401, which redisPipeline maps to null).
- Check Upstash latency/timeout metrics — if pipelines routinely approach 4s, the snapshot read needs a larger budget or the DB plan needs scaling.
- 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
- Distinguish 'Redis not configured' (config) from 'Redis request failed' (outage/timeout) — only the latter is retryable.
- Monitor Upstash latency; a pipeline that routinely nears the 4s budget will flap into null.
- Keep the Upstash token current; a revoked token returns 401 which redisPipeline maps to null.
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
- Redis snapshot lock failed
- Redis snapshot wait failed
- Redis snapshot lock retry failed
- Redis not configured
- Webhook URL DNS resolution failed: ${message}
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/8604e0c0e3c68c31.
Report an issue: GitHub.