{"record":{"id":"7b7ff6a6d7fb74d1","repo":"koala73/worldmonitor","slug":"redis-http-resp-status","errorCode":null,"errorMessage":"Redis HTTP ${resp.status}","messagePattern":"Redis HTTP (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"api/oauth/_refresh-recovery.ts","lineNumber":207,"sourceCode":"}\n\nasync function rawRedisEval(\n  script: string,\n  keys: string[],\n  args: Array<string | number>,\n): Promise<unknown> {\n  const { url, token } = redisConfig();\n  const resp = await fetch(`${url}/`, {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${token}`,\n      'Content-Type': 'application/json',\n      'User-Agent': 'worldmonitor-edge/1.0',\n    },\n    body: JSON.stringify(['EVAL', script, String(keys.length), ...keys, ...args]),\n    signal: AbortSignal.timeout(3_000),\n  });\n  if (!resp.ok) throw new Error(`Redis HTTP ${resp.status}`);\n  const data = (await resp.json().catch(() => null)) as { result?: unknown; error?: string } | null;\n  if (data?.error) throw new Error(`Redis EVAL failed: ${data.error}`);\n  if (!data || !Object.prototype.hasOwnProperty.call(data, 'result')) {\n    throw new Error('Redis EVAL returned an invalid response');\n  }\n  return data.result;\n}\n\nfunction parseStoredJson(value: unknown): unknown {\n  if (typeof value !== 'string') throw new Error('Redis returned an invalid stored value');\n  try {\n    return JSON.parse(value);\n  } catch {\n    throw new Error('Redis returned malformed JSON');\n  }\n}\n\nexport async function rawRedisBeginRefreshAttempt(","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/koala73/worldmonitor/blob/a96956387a927b8cd7aa34b0c41fca357e746be9/api/oauth/_refresh-recovery.ts#L189-L225","documentation":"Thrown by `rawRedisEval` in api/oauth/_refresh-recovery.ts:207 when the Upstash Redis REST API answers the EVAL POST with a non-2xx HTTP status. It signals a transport/auth-level failure rather than a script-level one (which comes back 200 with an `error` field). The fetch has a 3-second AbortSignal timeout, so timeouts surface as AbortError, not this message.","triggerScenarios":"An expired or wrong `UPSTASH_REDIS_REST_TOKEN` (401), a wrong `UPSTASH_REDIS_REST_URL` (404), rate limiting (429), or Upstash/platform 5xx outages during an OAuth refresh attempt EVAL call.","commonSituations":"Rotated Upstash credentials not updated in Vercel env; pointing the URL at a deleted database; hitting Upstash free-tier rate limits during refresh storms; transient Upstash incidents.","solutions":["Check the status: 401/403 means fix `UPSTASH_REDIS_REST_TOKEN`; 404 means fix `UPSTASH_REDIS_REST_URL`; 429 means rate limit — reduce call frequency or upgrade plan; 5xx is a transient Upstash issue — retry","Verify the URL points to the correct live Upstash database and the token belongs to it","Retry the OAuth refresh; the recovery flow is designed so a retryable failure does not lose the refresh token (restore/replay protection handles it)"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"function isRedisHttpError(e: unknown): e is Error & { status?: number } {\n  const m = /^Redis HTTP (\\d+)$/.exec(e instanceof Error ? e.message : '');\n  return m !== null;\n}","tryCatchPattern":"try {\n  await beginRefreshAttempt(token, id);\n} catch (e) {\n  if (isRedisHttpError(e)) {\n    const status = Number(/\\d+$/.exec(e.message)[0]);\n    if (status === 429 || status >= 500) await retryWithBackoff(); // transient\n    else reportConfigIssue(e); // 401/403/404 — fix credentials/URL\n  } else throw e;\n}","preventionTips":["Retry only 429/5xx with backoff; treat 401/404 as configuration failures","Monitor Upstash status and rate-limit headers; keep credentials in sync on rotation"],"tags":["redis","upstash","http-error","oauth","network"],"backgroundTag":"redis-connection-http-error","analyzedSha":"a96956387a927b8cd7aa34b0c41fca357e746be9","analyzedAt":"2026-08-27T19:53:08.521Z","schemaVersion":2},"datasetVersion":"2026-08-31T04:17:50.494Z"}