{"id":"52608c81bd5810ad","repo":"redis/redis-py","slug":"cannot-issue-a-watch-after-a-multi-52608c","errorCode":null,"errorMessage":"Cannot issue a WATCH after a MULTI","messagePattern":"Cannot issue a WATCH after a MULTI","errorType":"exception","errorClass":"RedisError","httpStatus":null,"severity":"error","filePath":"redis/cluster.py","lineNumber":4651,"sourceCode":"                    )\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):\n        if self._explicit_transaction:\n            raise RedisError(\"Cannot issue a WATCH after a MULTI\")\n\n        self._watching = True\n\n    def _immediate_execute_command(self, *args, **options):\n        return self._retry.call_with_retry(\n            lambda: self._get_connection_and_send_command(*args, **options),\n            self._reinitialize_on_error,\n            with_failure_count=True,\n        )\n\n    def _get_connection_and_send_command(self, *args, **options):\n        redis_node, connection = self._get_client_and_connection_for_transaction()\n\n        # Start timing for observability\n        start_time = time.monotonic()\n\n        try:\n            response = self._send_command_parse_response(","sourceCodeStart":4633,"sourceCodeEnd":4669,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/cluster.py#L4633-L4669","documentation":"Raised in TransactionStrategy._validate_watch (redis/cluster.py:4651) as RedisError when WATCH is called after MULTI has already started an explicit transaction. Per Redis semantics, WATCH must be issued before MULTI; once MULTI begins, the watched-keys set is frozen until EXEC/UNWATCH.","triggerScenarios":"pipe.multi(); pipe.watch('k'). The _explicit_transaction flag is True when multi() ran, so the subsequent WATCH call hits _validate_watch and raises.","commonSituations":"Reordering optimistic-locking code so MULTI precedes WATCH. Copy-pasting transaction bodies that append WATCH calls. Race-free intent gone wrong: calling MULTI early then trying to add watches.","solutions":["Issue WATCH before MULTI: pipe.watch('k'); val = pipe.get(...); pipe.multi(); pipe.set(...); pipe.execute().","If you must change watched keys, UNWATCH (or EXEC) first, then re-WATCH before a new MULTI.","Review the order of operations: every WATCH must precede the MULTI that opens the transaction."],"exampleFix":"# before\npipe = rc.pipeline(transaction=True)\npipe.multi()\npipe.watch('k')   # raises: Cannot issue a WATCH after a MULTI\n\n# after\npipe = rc.pipeline(transaction=True)\npipe.watch('k')    # WATCH BEFORE MULTI\npipe.multi()\npipe.set('k', 'v')\npipe.execute()","handlingStrategy":"validation","validationCode":"class TxBuilder:\n    def __init__(self, client):\n        self.pipe = client.pipeline(transaction=True)\n        self._multi_started = False\n    def watch(self, *keys):\n        if self._multi_started:\n            raise RuntimeError('WATCH after MULTI is not allowed')\n        self.pipe.watch(*keys)\n    def multi(self):\n        self._multi_started = True\n        self.pipe.multi()","typeGuard":null,"tryCatchPattern":"from redis.exceptions import RedisError\n\ntry:\n    pipe.watch('k')\nexcept RedisError as e:\n    if 'WATCH after a MULTI' in str(e):\n        pipe.unwatch()\n        pipe.watch('k')\n    else:\n        raise","preventionTips":["Always WATCH before MULTI; never reorder.","To re-watch mid-flow, UNWATCH (or EXEC) first, then WATCH, then MULTI again.","Encapsulate the ordering in a small builder/helper so callers cannot get it wrong."],"tags":["cluster","pipeline","transaction","optimistic-locking","ordering"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}