{"record":{"id":"bdc47d786b691c1b","repo":"koala73/worldmonitor","slug":"redis-eval-returned-an-invalid-response","errorCode":null,"errorMessage":"Redis EVAL returned an invalid response","messagePattern":"Redis EVAL returned an invalid response","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"api/oauth/_refresh-recovery.ts","lineNumber":211,"sourceCode":"  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(\n  refreshToken: string,\n  attemptId: string,\n): Promise<RefreshConsumeResult> {\n  const refreshKey = `oauth:refresh:${refreshToken}`;","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/koala73/worldmonitor/blob/a96956387a927b8cd7aa34b0c41fca357e746be9/api/oauth/_refresh-recovery.ts#L193-L229","documentation":"Thrown by `rawRedisEval` in api/oauth/_refresh-recovery.ts:211 when the Redis REST response is HTTP 200 with no `error`, but the parsed body is null or lacks a `result` property. This is a protocol-shape guard: Upstash always wraps command output in `{ \"result\": ... }`, so a body without it means an unexpected gateway response, a non-JSON body that `resp.json()` failed to parse (caught → null), or an API version/endpoint mismatch.","triggerScenarios":"Upstash returning an HTML error/blank page with 200 (proxy or gateway quirk); `resp.json()` throwing and yielding null; a URL pointing at a non-Upstash endpoint that answers 200 with a different JSON shape.","commonSituations":"`UPSTASH_REDIS_REST_URL` pointing at a proxy or wrong service that returns 200 with different JSON; transient gateway oddities; Upstash API behavior changes.","solutions":["Confirm `UPSTASH_REDIS_REST_URL` is a genuine Upstash REST endpoint (ends with `.upstash.io`, no path suffix beyond what the client appends)","Log/replicate the exact POST body (`['EVAL', ...]`) with curl against the configured URL to see the raw response shape","If transient, retry — the recovery flow treats this as a retryable failure and preserves the refresh token"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"function isInvalidRedisResponse(e: unknown): boolean {\n  return e instanceof Error && e.message === 'Redis EVAL returned an invalid response';\n}","tryCatchPattern":"try {\n  await beginRefreshAttempt(token, id);\n} catch (e) {\n  if (isInvalidRedisResponse(e)) {\n    await retryWithBackoff(); // usually transient gateway/shape issue; retryable-safe by design\n  } else throw e;\n}","preventionTips":["Point UPSTASH_REDIS_REST_URL directly at the Upstash host; avoid transforming proxies","Alert on reply-shape errors — repeated occurrences indicate endpoint or API changes"],"tags":["redis","upstash","response-parsing","protocol","oauth"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"a96956387a927b8cd7aa34b0c41fca357e746be9","analyzedAt":"2026-08-27T19:53:08.521Z","schemaVersion":2},"datasetVersion":"2026-08-31T04:17:50.494Z"}