{"record":{"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":3204,"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":3186,"sourceCodeEnd":3222,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/asyncio/cluster.py#L3186-L3222","documentation":"RedisError raised by TransactionStrategy._validate_watch (redis/asyncio/cluster.py:3202) when WATCH is called after MULTI was explicitly started. This mirrors core Redis semantics: WATCH after MULTI has no effect and is illegal. The library rejects it client-side before sending.","triggerScenarios":"`pipe.multi(); await pipe.watch('k1')` on a ClusterPipeline.","commonSituations":"Reordering pipeline setup code so MULTI precedes WATCH; copy-paste from a standalone flow that tolerated the ordering differently.","solutions":["Call watch() before multi(): WATCH then MULTI then queued commands then EXEC","Restructure so all WATCH calls happen first, then start the transaction with MULTI"],"exampleFix":"// before\npipe.multi()\nawait pipe.watch('k1')\n// after\nawait pipe.watch('k1')\npipe.multi()","handlingStrategy":"validation","validationCode":"# enforce ordering before building the pipeline\nassert not pipe._explicit_transaction, 'call watch() before multi()'","typeGuard":null,"tryCatchPattern":"try:\n    await pipe.watch('k1')\nexcept RedisError as e:\n    if 'WATCH after a MULTI' in str(e):\n        # reorder: reset, watch first, then multi","preventionTips":["Always call WATCH before MULTI","Encapsulate the watch/multi/exec sequence in a helper that enforces ordering"],"tags":["cluster","pipeline","transaction","watch","multi"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}