{"id":"59c7013634669214","repo":"redis/redis-py","slug":"at-least-a-command-with-a-key-is-needed-to-identif","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/asyncio/cluster.py","lineNumber":3097,"sourceCode":"        self._executing = False\n        self._retry = copy(self._pipe.cluster_client.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(\n        self,\n    ) -> Tuple[ClusterNode, 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._pipe.cluster_client.nodes_manager.get_node_from_slot(\n            list(self._pipeline_slots)[0], False\n        )\n        self._transaction_node = node\n\n        if not self._transaction_connection:\n            connection: Connection = self._transaction_node.acquire_connection()\n            self._transaction_connection = connection\n\n        return self._transaction_node, self._transaction_connection\n\n    def execute_command(self, *args: Union[KeyT, EncodableT], **kwargs: Any) -> \"Any\":\n        # Given the limitation of ClusterPipeline sync API, we have to run it in thread.\n        response = None\n        error = None","sourceCodeStart":3079,"sourceCodeEnd":3115,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/cluster.py#L3079-L3115","documentation":"Thrown by ClusterPipeline._get_client_and_connection_for_transaction when self._pipeline_slots is empty. A cluster transaction must be routed to a single node, which is chosen from the slot of the first queued key; with no key-bearing command in the pipeline the library cannot decide which node owns the transaction. This is a programming-error guard, not a transient runtime fault.","triggerScenarios":"Calling .execute() (or any execution path that reaches _get_client_and_connection_for_transaction) on an asyncio Redis Cluster pipeline that has only queued keyless commands (e.g. pipeline.config_get(), pipeline.info()) or has an empty command queue while _watching/_pipeline_slots are unset.","commonSituations":"Building a cluster pipeline out of only administrative commands (INFO, CONFIG, CLIENT, FLUSHDB), or forgetting to queue the actual key command before execute() in a transactional block, or calling WATCH with no subsequent key command.","solutions":["Queue at least one command whose first arg is a key (e.g. pipeline.set('k','v')) so the pipeline can resolve a slot before execute().","If you only need keyless/admin commands, use the regular cluster client (await client.config_get()) instead of a transactional pipeline.","If you must run keyless commands atomically, route them explicitly to one node via client.get_node_from_key or a standalone connection rather than the cluster pipeline."],"exampleFix":"// before\npipe = client.pipeline(transaction=True)\nawait pipe.config_get()\nawait pipe.execute()  # raises [80]\n// after\nawait client.config_get()  # no transaction needed","handlingStrategy":"validation","validationCode":"# Before execute(), ensure at least one key-bearing command is queued\nhas_key_cmd = any(\n    cmd.args and cmd.args[0] not in NO_SLOTS_COMMANDS\n    for cmd in pipe._command_queue\n)\nif not has_key_cmd:\n    raise ValueError('Cluster transaction needs at least one key command')","typeGuard":null,"tryCatchPattern":"from redis.exceptions import RedisClusterException\ntry:\n    await pipe.execute()\nexcept RedisClusterException as e:\n    if 'At least a command with a key' in str(e):\n        # add a key-bearing command or skip the transaction\n        ...","preventionTips":["Always queue at least one key command in a transactional cluster pipeline.","Use the regular client for admin/keyless commands instead of a transaction."],"tags":["cluster","pipeline","transaction","routing"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}