{"record":{"id":"bca211aa0091a405","repo":"koala73/worldmonitor","slug":"redis-transaction-failed-http-resp-status-text-slice-0-200","errorCode":null,"errorMessage":"Redis transaction failed: HTTP ${resp.status} — ${text.slice(0, 200)}","messagePattern":"Redis transaction failed: HTTP (.+?) — (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/seed-portwatch-port-activity.mjs","lineNumber":1012,"sourceCode":"  }\n  if (commands.length === 0) return [];\n\n  // Upstash /pipeline preserves command order but is explicitly non-atomic.\n  // The canonical list and seed-meta are the publication pointers, so they\n  // must commit in the same transaction as the per-country state they name.\n  const resp = await fetchFn(`${credentials.url}/multi-exec`, {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${credentials.token}`,\n      'Content-Type': 'application/json',\n      'User-Agent': CHROME_UA,\n    },\n    body: JSON.stringify(commands),\n    signal: AbortSignal.timeout(30_000),\n  });\n  if (!resp.ok) {\n    const text = await resp.text().catch(() => '');\n    throw new Error(`Redis transaction failed: HTTP ${resp.status} — ${text.slice(0, 200)}`);\n  }\n  const results = await resp.json();\n  if (!Array.isArray(results) || results.length !== commands.length) {\n    throw new Error(`Redis transaction failed: ${results?.error || 'invalid response'}`);\n  }\n  const failures = results.filter((result) => result?.error || result?.result === 'ERR');\n  if (failures.length > 0) {\n    throw new Error(`Redis transaction: ${failures.length}/${commands.length} commands failed`);\n  }\n  return results;\n}\n\nconst CORRUPT_COUNTRY_CACHE = Symbol('corrupt country cache');\n\n// MGET-style batch read via the Upstash REST /pipeline endpoint. Returns an\n// array aligned with `keys` where each element is either the parsed JSON\n// payload, explicit miss, or confirmed corrupt value. Transport/envelope errors\n// remain fatal: only a validated upstream replacement may overwrite corruption.","sourceCodeStart":994,"sourceCodeEnd":1030,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/seed-portwatch-port-activity.mjs#L994-L1030","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst resp = await fetch(url, { method: 'POST', body: JSON.stringify(commands), signal: AbortSignal.timeout(30_000) });\nif (!resp.ok) {\n  const text = await resp.text().catch(() => '');\n  throw new Error(`Redis transaction failed: HTTP ${resp.status} — ${text.slice(0, 200)}`);\n}\n// after\nlet resp;\nfor (let attempt = 0; attempt < 3; attempt++) {\n  resp = await fetch(url, { method: 'POST', body: JSON.stringify(chunk), signal: AbortSignal.timeout(30_000) });\n  if (resp.ok) break;\n  if (resp.status === 401 || resp.status === 403) {\n    throw new Error(`Redis credentials rejected: HTTP ${resp.status}`);\n  }\n  await new Promise((r) => setTimeout(r, 2 ** attempt * 500));\n}\nif (!resp?.ok) throw new Error(`Redis transaction failed after retries: HTTP ${resp?.status}`);","handlingStrategy":"retry","validationCode":"function assertUpstashEnv() {\n  if (!process.env.UPSTASH_REDIS_REST_URL || !process.env.UPSTASH_REDIS_REST_TOKEN) {\n    throw new Error('UPSTASH_REDIS_REST_URL/TOKEN missing');\n  }\n  new URL(process.env.UPSTASH_REDIS_REST_URL);\n}","typeGuard":"const isUpstashHttpError = (e) => e instanceof Error && e.message.startsWith('Redis transaction failed: HTTP');","tryCatchPattern":"try {\n  await redisPipeline(commands);\n} catch (err) {\n  if (/Redis transaction failed: HTTP 4(01|03)/.test(err.message)) {\n    throw new Error('Upstash credentials invalid — fix env and abort');\n  }\n  if (/HTTP 429|HTTP 5\\d\\d/.test(err.message)) {\n    return retryWithBackoff(() => redisPipeline(commands));\n  }\n  throw err;\n}","preventionTips":["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()"],"tags":["redis","http","upstash","network","seeding"],"backgroundTag":"http-error-response","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}