{"record":{"id":"621e33d293793041","repo":"redis/node-redis","slug":"scan-unknown-cursor-cursorarg-cluster-wide","errorCode":null,"errorMessage":"SCAN: unknown cursor \"${cursorArg}\". Cluster-wide SCAN cursors are minted per client instance and expire when idle — restart the scan from 0.","messagePattern":"SCAN: unknown cursor \"(.+?)\"\\. Cluster-wide SCAN cursors are minted per client instance and expire when idle — restart the scan from 0\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/client/lib/cluster/request-response-policies/scan-cursor.ts","lineNumber":46,"sourceCode":" */\nexport const routeScan: RequestRouter = async (slots, parser) => {\n  // Malformed raw command (missing cursor): forward to any node so the server\n  // returns its own arity error instead of a client-side TypeError.\n  if (parser.redisArgs.length < 2) {\n    return [{ client: await slots.nodeClient(slots.getRandomNode()) }];\n  }\n\n  const cursorArg = argToString(parser.redisArgs[1]);\n\n  if (cursorArg === '0') {\n    const address = slots.nextScanTarget(EMPTY_VISITED);\n    if (!address) throw new Error('SCAN: no master nodes available');\n    return [{ client: await pinnedMaster(slots, address) }];\n  }\n\n  const entry = slots.lookupScanCursor(cursorArg);\n  if (!entry) {\n    throw new Error(\n      `SCAN: unknown cursor \"${cursorArg}\". Cluster-wide SCAN cursors are ` +\n      `minted per client instance and expire when idle — restart the scan from 0.`\n    );\n  }\n  return [{\n    client: await pinnedMaster(slots, entry.address),\n    parser: withCursor(parser, entry.cursor)\n  }];\n};\n\nconst EMPTY_VISITED: ReadonlySet<string> = new Set();\n\nasync function pinnedMaster(slots: ClusterSlots, address: string) {\n  const client = await slots.getMasterByAddress(address);\n  if (!client) {\n    throw new Error(\n      `SCAN: node ${address} serving this cursor has left the cluster — ` +\n      `restart the scan from 0.`","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/client/lib/cluster/request-response-policies/scan-cursor.ts#L28-L64","documentation":"The cursor token passed to a cluster-wide SCAN does not match any client-minted cursor. In cluster mode, SCAN cursors are opaque virtual tokens created per client instance; they expire when idle and are not shared across instances. A stale or foreign cursor cannot be resolved to a (node, real-cursor) pair.","triggerScenarios":"Passing a cursor from a previous SCAN that has expired, a cursor from a different client instance, or a cursor after the client reconnected and lost its cursor state.","commonSituations":"Long pause between SCAN pages exceeding cursor expiry; reusing a cursor from a previous application run; client reconnected (slots refreshed) and internal cursor store was cleared; load balancer routing requests to different client instances.","solutions":["Restart the scan from cursor '0' when this error is caught","Avoid long delays between SCAN pages — process pages promptly","Increase SCAN COUNT to reduce round trips and complete faster","Do not persist cursors across application restarts or share across client instances"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"async function scanFromZero(client, fn) {\n  for (;;) {\n    try {\n      let cursor = '0';\n      do {\n        const reply = await client.scan(cursor);\n        await fn(reply.keys);\n        cursor = reply.cursor;\n      } while (cursor !== '0');\n      return;\n    } catch (e) {\n      if (e.message.startsWith('SCAN: unknown cursor')) {\n        continue; // restart from 0\n      }\n      throw e;\n    }\n  }\n}","preventionTips":["Always be prepared to restart a cluster-wide SCAN from cursor '0'","Do not persist cursor tokens across application restarts or share across client instances","Process SCAN pages promptly to avoid cursor expiry","Increase COUNT to reduce round trips and total scan duration"],"tags":["cluster","scan","cursor","state"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}