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

  1. Print results (the parsed body) to see the embedded error field returned by Upstash
  2. Confirm the URL targets the Upstash REST pipeline endpoint (/pipeline) and the payload is an array of [command, ...args] arrays
  3. Check every command's arguments are JSON-serializable strings/numbers (no undefined or nested objects where strings are expected)
  4. Remove or replace any unsupported Redis commands for the REST API
  5. 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

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


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)