{"record":{"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":4704,"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":4686,"sourceCodeEnd":4722,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/cluster.py#L4686-L4722","documentation":"Raised by TransactionStrategy._validate_watch (redis/cluster.py:4704) when WATCH is called after MULTI has already started the transaction. Per Redis semantics WATCH inside a MULTI is illegal (Redis returns an error); the cluster client enforces this client-side. Once _explicit_transaction is True, no further WATCH is allowed.","triggerScenarios":"In a transactional pipeline, calling pipe.multi() (or queuing commands that implicitly start MULTI) and then calling pipe.watch(key) afterward.","commonSituations":"Reordering WATCH/MULTI when porting standalone code; conditional logic that adds a watch late; misunderstanding that WATCH must precede MULTI.","solutions":["Always call watch() before multi()/transactional queuing begins.","Restructure so all watched keys are known up front, then start MULTI.","If conditions change mid-transaction, discard and restart: DISCARD, re-WATCH, re-MULTI."],"exampleFix":"// before\nwith rc.pipeline(transaction=True) as pipe:\n    pipe.multi()\n    pipe.watch('k')\n    pipe.set('k', 'v')\n// after\nwith rc.pipeline(transaction=True) as pipe:\n    pipe.watch('k')\n    pipe.multi()\n    pipe.set('k', 'v')","handlingStrategy":"validation","validationCode":"with rc.pipeline(transaction=True) as pipe:\n    pipe.watch('k')   # WATCH BEFORE MULTI\n    pipe.multi()\n    pipe.set('k', 'v')","typeGuard":"def watch_before_multi(sequence) -> bool:\n    return sequence.index('watch') < sequence.index('multi')","tryCatchPattern":"from redis.exceptions import RedisError\ntry:\n    pipe.watch('k')\nexcept RedisError as e:\n    if 'Cannot issue a WATCH after a MULTI' in str(e):\n        pipe.discard()\n        pipe.watch('k')\n        pipe.multi()","preventionTips":["Always WATCH before MULTI.","Restart (DISCARD -> WATCH -> MULTI) if conditions change mid-transaction."],"tags":["cluster","pipeline","transaction","optimistic-locking","api-misuse"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}