{"id":"9c9d1eb1497eea1d","repo":"redis/redis-py","slug":"at-least-a-command-with-a-key-is-needed-to-identif-9c9d1e","errorCode":null,"errorMessage":"At least a command with a key is needed to identify a node","messagePattern":"At least a command with a key is needed to identify a node","errorType":"exception","errorClass":"RedisClusterException","httpStatus":null,"severity":"error","filePath":"redis/cluster.py","lineNumber":4595,"sourceCode":"        self._pipeline_slots: Set[int] = set()\n        self._transaction_connection: Optional[Connection] = None\n        self._executing = False\n        self._retry = copy(self._pipe.retry)\n        self._retry.update_supported_errors(\n            RedisCluster.ERRORS_ALLOW_RETRY + self.SLOT_REDIRECT_ERRORS\n        )\n\n    def _get_client_and_connection_for_transaction(self) -> Tuple[Redis, Connection]:\n        \"\"\"\n        Find a connection for a pipeline transaction.\n\n        For running an atomic transaction, watch keys ensure that contents have not been\n        altered as long as the watch commands for those keys were sent over the same\n        connection. So once we start watching a key, we fetch a connection to the\n        node that owns that slot and reuse it.\n        \"\"\"\n        if not self._pipeline_slots:\n            raise RedisClusterException(\n                \"At least a command with a key is needed to identify a node\"\n            )\n\n        node: ClusterNode = self._nodes_manager.get_node_from_slot(\n            list(self._pipeline_slots)[0], False\n        )\n        redis_node: Redis = self._pipe.get_redis_connection(node)\n        if self._transaction_connection:\n            if not redis_node.connection_pool.owns_connection(\n                self._transaction_connection\n            ):\n                previous_node = self._nodes_manager.find_connection_owner(\n                    self._transaction_connection\n                )\n                previous_node.connection_pool.release(self._transaction_connection)\n                self._transaction_connection = None\n\n        if not self._transaction_connection:","sourceCodeStart":4577,"sourceCodeEnd":4613,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/cluster.py#L4577-L4613","documentation":"Raised in TransactionStrategy._get_client_and_connection_for_transaction (redis/cluster.py:4594) when self._pipeline_slots is empty. A cluster transaction must pin to a single slot's owning node; the slot is learned from the first keyed command. If a transactional operation needs a connection before any keyed command has been queued/watched, the strategy cannot pick a node.","triggerScenarios":"Calling pipe.execute() on a transactional pipeline that contains only keyless commands (e.g. pipe.multi(); pipe.info(); pipe.execute()). Or calling an immediate-execute command that needs a connection before any slot has been recorded. WATCH with no key followed by an operation requiring a connection.","commonSituations":"Building a transaction from admin/keyless commands, which have no slot. Calling MULTI then immediately EXEC with nothing keyed in between. Logic that begins a transaction conditionally and sometimes queues no keyed command.","solutions":["Ensure at least one keyed command (GET/SET/HGET/...) is queued or watched before execute().","Move keyless/admin commands (INFO, CONFIG, CLIENT) out of the transactional pipeline and call them on the client directly with target_nodes.","If you only needed a MULTI/EXEC wrapper for keyless commands, drop the pipeline and call the client directly."],"exampleFix":"# before\npipe = rc.pipeline(transaction=True)\npipe.multi()\npipe.info()           # keyless -> no slot\npipe.execute()        # raises: At least a command with a key is needed\n\n# after\nrc.info(target_nodes=RedisCluster.PRIMARIES)  # admin cmd on client\n# or include a keyed command in the transaction:\npipe = rc.pipeline(transaction=True)\npipe.watch('k')\npipe.multi()\npipe.set('k', '1')\npipe.execute()","handlingStrategy":"validation","validationCode":"def run_transaction(client, keyed_commands, keyless_commands=None):\n    if not keyed_commands:\n        raise ValueError('cluster transaction needs at least one keyed command to pin a node')\n    pipe = client.pipeline(transaction=True)\n    for args in keyed_commands:\n        pipe.execute_command(*args)\n    pipe.execute()\n    # run keyless commands on the client, not in the transaction\n    return pipe","typeGuard":null,"tryCatchPattern":"from redis.exceptions import RedisClusterException\n\ntry:\n    pipe.execute()\nexcept RedisClusterException as e:\n    if 'command with a key is needed' in str(e):\n        rc.info(target_nodes=RedisCluster.PRIMARIES)  # move keyless cmd out of the txn\n    else:\n        raise","preventionTips":["Ensure at least one keyed command is queued/watched before execute() in a transaction.","Keep admin/keyless commands out of cluster transactions; call them on the client with target_nodes.","Validate that your transaction body is non-empty and contains a slottable command."],"tags":["cluster","pipeline","transaction","routing"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}