{"record":{"id":"b2a6c4afbd2f81f0","repo":"koala73/worldmonitor","slug":"redis-refresh-attempt-restore-returned-an-invalid","errorCode":null,"errorMessage":"Redis refresh-attempt restore returned an invalid response","messagePattern":"Redis refresh-attempt restore returned an invalid response","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"api/oauth/_refresh-recovery.ts","lineNumber":293,"sourceCode":"    \"if ARGV[3] ~= '' then redis.call('SET', KEYS[2], ARGV[3], 'EX', ARGV[4]) end\",\n    \"redis.call('DEL', KEYS[3])\",\n    'return 1',\n  ].join('\\n');\n  const result = await rawRedisEval(\n    script,\n    [\n      `oauth:refresh:${refreshToken}`,\n      refreshFamilyPointerKey(refreshToken),\n      refreshFamilyAttemptKey(refreshToken),\n    ],\n    [\n      attemptValue,\n      JSON.stringify(refreshData),\n      familyId ? serializeRefreshFamilyPointer(familyId) : '',\n      REFRESH_TTL_SECONDS,\n    ],\n  );\n  if (result !== 0 && result !== 1) throw new Error('Redis refresh-attempt restore returned an invalid response');\n  return result === 1;\n}\n\nexport async function rawRedisFinalizeRefreshAttempt(\n  refreshToken: string,\n  attemptValue: string,\n): Promise<boolean> {\n  const script = [\n    \"if redis.call('GET', KEYS[2]) ~= ARGV[1] then return 0 end\",\n    \"if redis.call('GET', KEYS[1]) == ARGV[2] then redis.call('DEL', KEYS[1]) end\",\n    \"redis.call('DEL', KEYS[2])\",\n    'return 1',\n  ].join('\\n');\n  const result = await rawRedisEval(\n    script,\n    [`oauth:refresh:${refreshToken}`, refreshFamilyAttemptKey(refreshToken)],\n    [attemptValue, serializeRefreshAttemptMarker(attemptValue)],\n  );","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/koala73/worldmonitor/blob/a96956387a927b8cd7aa34b0c41fca357e746be9/api/oauth/_refresh-recovery.ts#L275-L311","documentation":"Thrown by `rawRedisRestoreRefreshAttempt` in api/oauth/_refresh-recovery.ts:293 when the restore EVAL returns something other than the integer 0 or 1. The restore Lua script is a compare-and-set that can only return 0 (attempt key mismatch) or 1 (restored), so any other value means the Redis reply was mangled or mis-encoded, not that the restore logically failed (a logical failure is a clean 0 → `false`).","triggerScenarios":"A restore call after a failed OAuth refresh finalization where the EVAL response payload is not a bare 0/1 — reply re-encoding by a proxy, Upstash REST format change, or corrupted JSON body.","commonSituations":"Same family as other reply-shape guards: Upstash behavior changes, intermediary proxies, or version skew; rare and infrastructure-flavored rather than caller-induced.","solutions":["Log the raw EVAL response to identify what was returned instead of 0/1","Verify the Upstash REST endpoint is reached directly with no body-transforming proxy","Treat as transient/retryable: the caller (restoreRefreshAttempt flow) already catches this and keeps the retryable OAuth response, so a retry after the infra issue clears is safe"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"function isInvalidRestoreResponse(e: unknown): boolean {\n  return e instanceof Error && e.message === 'Redis refresh-attempt restore returned an invalid response';\n}","tryCatchPattern":"try {\n  await restoreRefreshAttempt(token, attemptValue, data, familyId);\n} catch (e) {\n  if (isInvalidRestoreResponse(e)) {\n    // keep the retryable OAuth response and retry after infra recovery;\n    // a committed restore is idempotent-safe (compare-and-set guards replay)\n    await retryWithBackoff();\n  } else throw e;\n}","preventionTips":["Remember a logical mismatch returns false cleanly — this throw always means infrastructure/encoding, so classify it as transient","Pair retries with observability so repeated shape errors trigger an infra investigation"],"tags":["redis","upstash","protocol","oauth","response-parsing"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"a96956387a927b8cd7aa34b0c41fca357e746be9","analyzedAt":"2026-08-27T19:53:08.521Z","schemaVersion":2},"datasetVersion":"2026-08-31T04:17:50.494Z"}