{"id":"c07556e59b101c81","repo":"redis/redis-py","slug":"cannot-issue-a-watch-after-a-multi-c07556","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/asyncio/cluster.py","lineNumber":3172,"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 super().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    async def _immediate_execute_command(self, *args, **options):\n        return await 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    async def _get_connection_and_send_command(self, *args, **options):\n        redis_node, connection = self._get_client_and_connection_for_transaction()\n        # Only disconnect if not watching - disconnecting would lose WATCH state\n        if not self._watching:\n            await redis_node.disconnect_if_needed(connection)\n\n        # Start timing for observability\n        start_time = time.monotonic()","sourceCodeStart":3154,"sourceCodeEnd":3190,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/cluster.py#L3154-L3190","documentation":"Raised by _validate_watch (as the base RedisError) when WATCH is issued after MULTI has already started an explicit transaction. In Redis, WATCH after MULTI is illegal — keys must be watched before MULTI begins — so the library mirrors that rule on the client side.","triggerScenarios":"Calling pipe.multi() (which sets _explicit_transaction) and then pipe.watch('k') on a cluster pipeline; any code path that issues WATCH once the explicit MULTI flag is set.","commonSituations":"Porting standalone-pipeline code that interleaves WATCH and MULTI in the wrong order; building a pipeline dynamically where WATCH is conditionally added after MULTI was already begun.","solutions":["Call watch() before multi(): WATCH the keys first, then start the transaction with multi().","Restructure dynamic pipelines so all WATCH calls are decided and issued before MULTI.","If you no longer need to watch, call pipe.unwatch() (or reset the pipeline) instead of adding a new WATCH mid-transaction."],"exampleFix":"// before\nawait pipe.multi()\nawait pipe.watch('k')  # raises [83]\n// after\nawait pipe.watch('k')\nawait pipe.multi()","handlingStrategy":"validation","validationCode":"if pipe._explicit_transaction:\n    raise ValueError('WATCH must be issued before MULTI')","typeGuard":null,"tryCatchPattern":"from redis.exceptions import RedisError\ntry:\n    await pipe.watch('k')\nexcept RedisError as e:\n    if 'Cannot issue a WATCH after a MULTI' in str(e):\n        pipe.reset(); await pipe.watch('k')  # restart in correct order","preventionTips":["Always WATCH before MULTI.","Decide watch keys up front so they aren't added conditionally after MULTI."],"tags":["cluster","pipeline","transaction","watch","ordering"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}