{"record":{"id":"5e3afe4b77bb4806","repo":"redis/redis-py","slug":"cannot-execute-ft-cursor-commands-without-ft-aggre-5e3afe","errorCode":null,"errorMessage":"Cannot execute FT.CURSOR commands without FT.AGGREGATE","messagePattern":"Cannot execute FT\\.CURSOR commands without FT\\.AGGREGATE","errorType":"exception","errorClass":"RedisClusterException","httpStatus":null,"severity":"error","filePath":"redis/cluster.py","lineNumber":1123,"sourceCode":"        keys = self._get_command_keys(*args)\n        commands = []\n\n        for key in keys:\n            commands.append(\n                {\n                    \"args\": (args[0], key),\n                    \"kwargs\": kwargs,\n                }\n            )\n\n        return commands\n\n    def get_special_nodes(self) -> Optional[list[\"ClusterNode\"]]:\n        \"\"\"\n        Returns a list of nodes for commands with a special policy.\n        \"\"\"\n        if not self._aggregate_nodes:\n            raise RedisClusterException(\n                \"Cannot execute FT.CURSOR commands without FT.AGGREGATE\"\n            )\n\n        return self._aggregate_nodes\n\n    def get_random_primary_node(self) -> \"ClusterNode\":\n        \"\"\"\n        Returns a random primary node\n        \"\"\"\n        return random.choice(self.get_primaries())\n\n    def _evaluate_all_succeeded(self, res):\n        \"\"\"\n        Evaluate the result of a command with ResponsePolicy.ALL_SUCCEEDED\n        \"\"\"\n        first_successful_response = None\n\n        if isinstance(res, dict):","sourceCodeStart":1105,"sourceCodeEnd":1141,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/cluster.py#L1105-L1141","documentation":"Raised as a RedisClusterException by get_special_nodes() when FT.CURSOR READ or FT.CURSOR DEL is attempted before FT.AGGREGATE has been executed in the current client session. The client stores the aggregate target nodes in self._aggregate_nodes only when it processes an FT.AGGREGATE command (cluster.py:1369-1370), and FT.CURSOR must route to those same nodes. Without a prior FT.AGGREGATE, there is no valid target node set for the cursor.","triggerScenarios":"Calling r.execute_command('FT.CURSOR READ', index, cursor_id, ...) or the ft().cursor_read() helper on a RedisCluster instance without first running an FT.AGGREGATE on that same instance, so self._aggregate_nodes is still None (initialized at cluster.py:983).","commonSituations":"Developer calls FT.CURSOR to read the next page of results from a cursor obtained in a previous session or from a different client instance, or the FT.AGGREGATE was run on a different RedisCluster object so _aggregate_nodes was never populated.","solutions":["Run the FT.AGGREGATE query on the same RedisCluster instance before issuing any FT.CURSOR command so _aggregate_nodes is populated.","If you need to page results across restarts, re-run FT.AGGREGATE with WITHCURSOR to get a fresh cursor on the current client.","Do not share cursor IDs between different client instances; each instance must own its own aggregate+cursor lifecycle."],"exampleFix":"// before\nr.execute_command('FT.CURSOR READ', 'myidx', cursor_id)\n\n// after\nr.execute_command('FT.AGGREGATE', 'myidx', '*', 'WITHCURSOR', 'COUNT', 10)\nr.execute_command('FT.CURSOR READ', 'myidx', cursor_id)","handlingStrategy":"validation","validationCode":"# Ensure FT.AGGREGATE runs before FT.CURSOR on the same instance\nif not getattr(client, '_aggregate_nodes', None):\n    raise RuntimeError('Run FT.AGGREGATE with WITHCURSOR before FT.CURSOR')","typeGuard":"def has_aggregate_nodes(client) -> bool:\n    return getattr(client, '_aggregate_nodes', None) is not None","tryCatchPattern":"from redis.exceptions import RedisClusterException\ntry:\n    client.execute_command('FT.CURSOR READ', idx, cursor)\nexcept RedisClusterException as e:\n    if 'FT.AGGREGATE' in str(e):\n        client.execute_command('FT.AGGREGATE', idx, '*', 'WITHCURSOR')","preventionTips":["Always run FT.AGGREGATE with WITHCURSOR on the same client instance before paging with FT.CURSOR.","Do not persist cursor IDs across client restarts or different instances.","Keep the aggregate+cursor lifecycle within a single RedisCluster object."],"tags":["search","redistack","ft-aggregate","cluster-routing"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}