{"id":"84275e5ee37583e7","repo":"redis/redis-py","slug":"cannot-watch-or-send-commands-on-different-slots-84275e","errorCode":null,"errorMessage":"Cannot watch or send commands on different slots","messagePattern":"Cannot watch or send commands on different slots","errorType":"exception","errorClass":"CrossSlotTransactionError","httpStatus":null,"severity":"error","filePath":"redis/cluster.py","lineNumber":4631,"sourceCode":"        if not self._transaction_connection:\n            self._transaction_connection = get_connection(redis_node)\n\n        return redis_node, self._transaction_connection\n\n    def execute_command(self, *args, **kwargs):\n        slot_number: Optional[int] = None\n        if args[0] not in ClusterPipeline.NO_SLOTS_COMMANDS:\n            slot_number = self._pipe.determine_slot(*args)\n\n        if (\n            self._watching or args[0] in self.IMMEDIATE_EXECUTE_COMMANDS\n        ) and not self._explicit_transaction:\n            if args[0] == \"WATCH\":\n                self._validate_watch()\n\n            if slot_number is not None:\n                if self._pipeline_slots and slot_number not in self._pipeline_slots:\n                    raise CrossSlotTransactionError(\n                        \"Cannot watch or send commands on different slots\"\n                    )\n\n                self._pipeline_slots.add(slot_number)\n            elif args[0] not in self.NO_SLOTS_COMMANDS:\n                raise RedisClusterException(\n                    f\"Cannot identify slot number for command: {args[0]},\"\n                    \"it cannot be triggered in a transaction\"\n                )\n\n            return self._immediate_execute_command(*args, **kwargs)\n        else:\n            if slot_number is not None:\n                self._pipeline_slots.add(slot_number)\n\n            return self.pipeline_execute_command(*args, **kwargs)\n\n    def _validate_watch(self):","sourceCodeStart":4613,"sourceCodeEnd":4649,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/cluster.py#L4613-L4649","documentation":"Raised in TransactionStrategy.execute_command (redis/cluster.py:4630) as CrossSlotTransactionError when a watched/immediate command in a transaction hashes to a slot different from those already in self._pipeline_slots. Redis Cluster transactions are atomic only within a single hash slot, so mixing slots during WATCH or immediate execution is rejected.","triggerScenarios":"pipe.watch('user:1'); pipe.get('order:9') where the two keys hash to different slots. Or any immediate-execute command (WATCH/UNWATCH or a command issued while _watching is True and no MULTI yet) whose slot is not the one already recorded.","commonSituations":"Keys naively named without a shared hash tag. Porting multi-key WATCH/transaction logic from standalone redis to cluster. Using natural keys that span logical entities (user vs order) in one transaction.","solutions":["Put all keys of one transaction under a shared hash tag so they land in the same slot, e.g. 'user:{1}', 'order:{1}'.","Split the operation into multiple single-slot transactions.","Use a Lua script (via rc.eval) keyed on one slot to do multi-key logic atomically, again with a shared hash tag.","Re-check your keys' slots with rc.cluster_keyslot(key) before issuing WATCH."],"exampleFix":"# before\npipe = rc.pipeline(transaction=True)\npipe.watch('user:1')\npipe.multi()\npipe.set('order:1', 'x')   # different slot -> CrossSlotTransactionError\npipe.execute()\n\n# after (shared hash tag forces one slot)\npipe = rc.pipeline(transaction=True)\npipe.watch('{acct}:user')\npipe.multi()\npipe.set('{acct}:order', 'x')\npipe.execute()","handlingStrategy":"validation","validationCode":"from redis.cluster import RedisCluster\n\ndef assert_same_slot(client, keys):\n    slots = {client.cluster_keyslot(k) for k in keys}\n    if len(slots) != 1:\n        raise ValueError(f'keys span multiple slots: {slots}; use a shared hash tag')\n\ndef watch_and_mutate(client, watch_key, ops):\n    assert_same_slot(client, [watch_key] + [a[0] for a in ops])\n    pipe = client.pipeline(transaction=True)\n    pipe.watch(watch_key)\n    pipe.multi()\n    for args in ops:\n        pipe.execute_command(*args)\n    return pipe.execute()","typeGuard":null,"tryCatchPattern":"from redis.exceptions import RedisClusterException\n\ntry:\n    pipe.execute()\nexcept RedisClusterException as e:\n    if 'different slots' in str(e):\n        # re-issue keys under a shared hash tag, e.g. '{tag}:k'\n        ...\n    else:\n        raise","preventionTips":["Design keys with a shared hash tag ({tag}prefix) so multi-key transactions land on one slot.","Pre-flight with rc.cluster_keyslot(k) for every key in the transaction.","Split cross-entity operations into separate single-slot transactions."],"tags":["cluster","pipeline","transaction","hashslot","cross-slot"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}