koala73/worldmonitor · error · Error
Redis transaction failed: ${results?.error || 'invalid respo
Error message
Redis transaction failed: ${results?.error || 'invalid response'} What it means
Thrown by the same Upstash REST pipeline helper when the HTTP call succeeds (2xx) but the JSON body is not a well-formed transaction result: it is not an array, or its length does not equal the number of commands sent. This indicates Upstash accepted the request but returned an error object or unexpected payload (e.g. {error: ...} for an invalid command or wrong REST format).
Solutions
- Print results (the parsed body) to see the embedded error field returned by Upstash
- Confirm the URL targets the Upstash REST pipeline endpoint (/pipeline) and the payload is an array of [command, ...args] arrays
- Check every command's arguments are JSON-serializable strings/numbers (no undefined or nested objects where strings are expected)
- Remove or replace any unsupported Redis commands for the REST API
- Pin/verify Upstash API behavior against current docs if the envelope changed
Example fix
// before
const results = await resp.json();
if (!Array.isArray(results) || results.length !== commands.length) {
throw new Error(`Redis transaction failed: ${results?.error || 'invalid response'}`);
}
// after
const results = await resp.json().catch(() => null);
if (!Array.isArray(results) || results.length !== commands.length) {
throw new Error(`Redis transaction failed: ${results?.error || `unexpected body: ${JSON.stringify(results).slice(0, 200)}`}`);
} Defensive patterns
Strategy: type-guard
Validate before calling
function validateUpstashPipelineBody(body, expectedCount) {
return Array.isArray(body) && body.length === expectedCount && body.every((r) => r === null || typeof r === 'object');
} Type guard
const isPipelineResultArray = (v, n) => Array.isArray(v) && v.length === n && v.every((r) => typeof r === 'object' || r === null);
Try / catch
try {
const results = await redisPipeline(commands);
} catch (err) {
const bodyErr = err?.message.match(/Redis transaction failed: (.+)$/);
console.error('Upstash pipeline rejected request:', bodyErr?.[1] ?? err);
throw err;
} Prevention
- Always POST [cmd, ...args] arrays to the /pipeline endpoint
- Ensure every command argument is a JSON-serializable string or number
- Log the parsed body whenever the result shape check fails to surface Upstash's error field
- Test pipeline payloads against Upstash after any command-list change
When it happens
Trigger: Sending a command list Upstash rejects wholesale (e.g. a command not permitted by the REST API, malformed JSON in a serialized value), hitting an endpoint that is not the Upstash pipeline/publish URL, or the REST endpoint returning an error object {error:'...'} with HTTP 200 via a proxy.
Common situations: Pointing UPSTASH_REDIS_REST_URL at the wrong path or a non-Upstash service; using a command unsupported by Upstash REST; oversized request body silently rejected; Upstash API version change altering the response envelope.
Related errors
- Redis transaction failed: HTTP ${resp.status} — ${text.slice
- Redis not configured
- Redis HTTP ${resp.status}
- Redis EVAL failed: ${data.error}
- Redis EVAL returned an invalid response
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/38ea1b2de2da84b3.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/seed-portwatch-port-activity.mjs:1016
// 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.
// Primes the per-country cache lookup in one round-trip instead of 174 GETs.
async function redisMgetJson(keys) {
if (keys.length === 0) return [];
const commands = keys.map((k) => ['GET', k]);View on GitHub (pinned to 7d06c8633d)