koala73/worldmonitor · critical
Redis snapshot lock retry failed
Error message
Redis snapshot lock retry failed
What it means
Thrown inside the same wait loop when a SET NX retry (attempting to steal the lock if the prior holder died) returns null or a per-command error. The retry uses the shrinking redisTimeoutMs budget; a null/error here is treated as a Redis outage and bubbles to the 503 REDIS_DOWN catch.
Source
Thrown at api/health.js:2689
const refreshedResult = await redisPipeline([['GET', snapshotKey]], redisTimeoutMs);
if (!refreshedResult || refreshedResult[0]?.error) throw new Error('Redis snapshot wait failed');
const refreshedSnapshot = parseHealthVerdictSnapshot(refreshedResult[0]?.result, snapshotNow(), { requireChecks: !compact });
if (
refreshedSnapshot
&& !hasExpiredActivationGrace(refreshedSnapshot, snapshotNow())
) {
return healthResponse(refreshedSnapshot, compact, headers);
}
lockResult = await redisPipeline([[
'SET',
HEALTH_VERDICT_REFRESH_LOCK_KEY,
refreshLockToken,
'EX',
String(HEALTH_VERDICT_REFRESH_LOCK_TTL_SECONDS),
'NX',
]], redisTimeoutMs);
if (!lockResult || lockResult[0]?.error) throw new Error('Redis snapshot lock retry failed');
ownsSnapshotRefreshLock = lockResult[0]?.result === 'OK';
if (ownsSnapshotRefreshLock) break;
}
// Redis stayed reachable but another refresher held the lock through our
// request budget. Fall back to one direct sweep rather than mislabeling
// healthy Redis as REDIS_DOWN. This path is bounded and should be rare;
// the normal cold-burst path still permits only the elected owner.
}
} catch (err) {
if (ownsSnapshotRefreshLock) await releaseHealthVerdictRefreshLock(refreshLockToken);
return jsonResponse({
status: 'REDIS_DOWN',
error: err.message,
checkedAt: new Date(now).toISOString(),
}, 503, headers);
}
const allDataKeys = [View on GitHub (pinned to ffec79ac33)
Solutions
- Retry the health request after a few seconds.
- Verify the Upstash REST token is still valid and the URL is correct.
- Check Upstash dashboards for errors around the lock key (HEALTH_VERDICT_REFRESH_LOCK_KEY).
- If the lock key is in a wrong type state, DEL it so SET NX can succeed on retry.
Example fix
// before
if (!lockResult || lockResult[0]?.error) throw new Error('Redis snapshot lock retry failed');
// after — break the loop and fall through to the bounded direct sweep
if (!lockResult || lockResult[0]?.error) break; Defensive patterns
Strategy: try-catch
Try / catch
// Same pattern as [11]: break the loop and fall through to the bounded direct // sweep instead of throwing. lockResult = await redisPipeline([['SET', key, token, 'EX', ttl, 'NX']], redisTimeoutMs); if (!lockResult || lockResult[0]?.error) break;
Prevention
- Treat lock-retry errors as transient Redis issues, not lock-contention bugs.
- Keep the per-attempt timeout reasonable so a slow SET NX does not null out.
- Verify no other writer changes the type of HEALTH_VERDICT_REFRESH_LOCK_KEY.
When it happens
Trigger: During the 3s wait window, the loop re-attempts SET NX to take over the lock if the holder's lease lapsed; the SET NX pipeline returns null (timeout/HTTP error) or Upstash returns a per-command error on the SET.
Common situations: Upstash transient error mid-wait; the shrinking per-attempt timeout clipped the SET NX; an Upstash failover; token revoked mid-request.
Related errors
- Redis snapshot lock failed
- Redis snapshot wait failed
- Redis request 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/a6d7d38b58868d65.
Report an issue: GitHub.