koala73/worldmonitor · error · Error
Redis transaction failed: HTTP ${resp.status} — ${text.slice
Error message
Redis transaction failed: HTTP ${resp.status} — ${text.slice(0, 200)} What it means
Thrown by the Upstash Redis REST pipeline helper (redisPipeline) in scripts/seed-portwatch-port-activity.mjs when the HTTP response from the Redis REST endpoint has a non-2xx status. The thrown message embeds the HTTP status and the first 200 characters of the response body, which typically contains Upstash's error explanation (auth failure, rate limit, malformed command list).
Solutions
- Read the HTTP status and body snippet in the error message — 401/403 means credentials, 429 means throttling, 5xx means Upstash outage
- Verify UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN are loaded via loadEnvFile() and match the current database
- For 429, chunk the command list into smaller pipelines or add delay between requests
- Retry on 5xx/network errors with exponential backoff; do not retry 4xx auth errors
- Confirm network egress and that no proxy intercepts requests to the Upstash host
Example fix
// before
const resp = await fetch(url, { method: 'POST', body: JSON.stringify(commands), signal: AbortSignal.timeout(30_000) });
if (!resp.ok) {
const text = await resp.text().catch(() => '');
throw new Error(`Redis transaction failed: HTTP ${resp.status} — ${text.slice(0, 200)}`);
}
// after
let resp;
for (let attempt = 0; attempt < 3; attempt++) {
resp = await fetch(url, { method: 'POST', body: JSON.stringify(chunk), signal: AbortSignal.timeout(30_000) });
if (resp.ok) break;
if (resp.status === 401 || resp.status === 403) {
throw new Error(`Redis credentials rejected: HTTP ${resp.status}`);
}
await new Promise((r) => setTimeout(r, 2 ** attempt * 500));
}
if (!resp?.ok) throw new Error(`Redis transaction failed after retries: HTTP ${resp?.status}`); Defensive patterns
Strategy: retry
Validate before calling
function assertUpstashEnv() {
if (!process.env.UPSTASH_REDIS_REST_URL || !process.env.UPSTASH_REDIS_REST_TOKEN) {
throw new Error('UPSTASH_REDIS_REST_URL/TOKEN missing');
}
new URL(process.env.UPSTASH_REDIS_REST_URL);
} Type guard
const isUpstashHttpError = (e) => e instanceof Error && e.message.startsWith('Redis transaction failed: HTTP'); Try / catch
try {
await redisPipeline(commands);
} catch (err) {
if (/Redis transaction failed: HTTP 4(01|03)/.test(err.message)) {
throw new Error('Upstash credentials invalid — fix env and abort');
}
if (/HTTP 429|HTTP 5\d\d/.test(err.message)) {
return retryWithBackoff(() => redisPipeline(commands));
}
throw err;
} Prevention
- Verify UPSTASH_REDIS_REST_URL/TOKEN with a trivial GET before large seed runs
- Chunk pipelines to stay under Upstash QPS/body limits
- Add exponential backoff for 429/5xx but never retry 401/403
- Monitor Upstash status/limits; load credentials only via loadEnvFile()
When it happens
Trigger: Calling redisPipeline (used by redisMgetJson and cache writes) when the Upstash REST API returns 401 (bad token), 403, 429 (rate/QPS limit exceeded by the 174-key pipeline), 5xx outage, or a proxy/CDN error page instead of a JSON transaction result.
Common situations: Wrong or rotated UPSTASH_REDIS_REST_URL/TOKEN in env; free-tier QPS limits hit by large pipelines; Upstash region maintenance or incident; corporate proxy returning an HTML error page; Vercel/Railway env vars not loaded before the seed run.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Redis HTTP ${resp.status}
- Redis HTTP ${resp.status}
- GDACS ${res.status}
- Redis transaction failed: ${results?.error || 'invalid respo
- Redis not configured
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/bca211aa0091a405.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/seed-portwatch-port-activity.mjs:1012
}
if (commands.length === 0) return [];
// Upstash /pipeline preserves command order but is explicitly non-atomic.
// The canonical list and seed-meta are the publication pointers, so they
// must commit in the same transaction as the per-country state they name.
const resp = await fetchFn(`${credentials.url}/multi-exec`, {
method: 'POST',
headers: {
Authorization: `Bearer ${credentials.token}`,
'Content-Type': 'application/json',
'User-Agent': CHROME_UA,
},
body: JSON.stringify(commands),
signal: AbortSignal.timeout(30_000),
});
if (!resp.ok) {
const text = await resp.text().catch(() => '');
throw new Error(`Redis transaction failed: HTTP ${resp.status} — ${text.slice(0, 200)}`);
}
const results = await resp.json();
if (!Array.isArray(results) || results.length !== commands.length) {
throw new Error(`Redis transaction failed: ${results?.error || 'invalid response'}`);
}
const failures = results.filter((result) => result?.error || result?.result === 'ERR');
if (failures.length > 0) {
throw new Error(`Redis transaction: ${failures.length}/${commands.length} commands failed`);
}
return results;
}
const CORRUPT_COUNTRY_CACHE = Symbol('corrupt country cache');
// MGET-style batch read via the Upstash REST /pipeline endpoint. Returns an
// array aligned with `keys` where each element is either the parsed JSON
// payload, explicit miss, or confirmed corrupt value. Transport/envelope errors
// remain fatal: only a validated upstream replacement may overwrite corruption.View on GitHub (pinned to 7d06c8633d)