{"record":{"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":4648,"sourceCode":"            and not self._transaction_has_keyed_slot\n        ):\n            # Prior slots came only from zero-key scripts; retarget.\n            self._pipeline_slots.clear()\n        if slot_number is not None:\n            self._transaction_has_keyed_slot = True\n        return slot_number\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":4630,"sourceCodeEnd":4666,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/cluster.py#L4630-L4666","documentation":"Raised by TransactionStrategy._get_client_and_connection_for_transaction (redis/cluster.py:4648) when _pipeline_slots is empty. A cluster transaction must pin to a single slot to choose its connection; if no keyed command has established a slot yet (only slot-agnostic commands queued), the library cannot pick a node and aborts.","triggerScenarios":"Opening a transactional pipeline and calling execute() having queued only keyless/slot-agnostic commands (e.g. only PING, INFO, or zero-key EVAL), or calling watch()/immediate commands before any key has fixed a slot.","commonSituations":"Building a transaction dynamically and forgetting to include a keyed command; executing an empty-ish transaction for health checks; race where the first keyed command was skipped due to a condition.","solutions":["Ensure at least one keyed command (GET/SET/etc.) is queued before execute() so a slot is fixed.","If you only need keyless commands, use a non-transactional pipeline or execute directly on a node.","For WATCH, watch a key first so the slot is established before MULTI."],"exampleFix":"// before\nwith rc.pipeline(transaction=True) as pipe:\n    pipe.ping()\n    pipe.execute()\n// after\nwith rc.pipeline(transaction=True) as pipe:\n    pipe.set('anchor:{tag}', '1')\n    pipe.ping()\n    pipe.execute()","handlingStrategy":"validation","validationCode":"with rc.pipeline(transaction=True) as pipe:\n    if not any(cmd_has_key(c) for c in planned_commands):\n        pipe.set('anchor:{tag}', '1')  # ensure a slot is fixed\n    for c in planned_commands:\n        pipe.execute_command(*c)\n    pipe.execute()","typeGuard":"def has_keyed_command(commands) -> bool:\n    keyless = {'PING','INFO','DBSIZE','FLUSHALL','FLUSHDB'}\n    return any(c[0].upper() not in keyless for c in commands)","tryCatchPattern":"from redis.exceptions import RedisClusterException\ntry:\n    pipe.execute()\nexcept RedisClusterException as e:\n    if 'At least a command with a key' in str(e):\n        # queue a keyed command and retry, or use a non-transactional pipeline\n        ...","preventionTips":["Always queue at least one keyed command in a transactional pipeline.","Use non-transactional pipelines for purely keyless/admin work."],"tags":["cluster","pipeline","transaction","routing","cross-slot"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}