{"id":"deba33a2b31e3936","repo":"redis/redis-py","slug":"cannot-issue-a-watch-after-a-multi","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/client.py","lineNumber":2300,"sourceCode":"                network_peer_port=getattr(conn, \"port\", None),\n                error_type=e,\n                retry_attempts=actual_retry_attempts,\n                is_internal=False,\n            )\n            raise\n        finally:\n            await self.reset()\n\n    async def discard(self):\n        \"\"\"Flushes all previously queued commands\n        See: https://redis.io/commands/DISCARD\n        \"\"\"\n        await self.execute_command(\"DISCARD\")\n\n    async def watch(self, *names: KeyT):\n        \"\"\"Watches the values at keys ``names``\"\"\"\n        if self.explicit_transaction:\n            raise RedisError(\"Cannot issue a WATCH after a MULTI\")\n        return await self.execute_command(\"WATCH\", *names)\n\n    async def unwatch(self):\n        \"\"\"Unwatches all previously specified keys\"\"\"\n        return self.watching and await self.execute_command(\"UNWATCH\") or True\n","sourceCodeStart":2282,"sourceCodeEnd":2306,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/client.py#L2282-L2306","documentation":"Raised by Pipeline.watch() (redis/asyncio/client.py:2300) when self.explicit_transaction is True. Redis semantics forbid WATCH after MULTI has started; once a transaction is open the watched-key set is fixed. The library enforces this client-side and raises RedisError before sending anything to the server.","triggerScenarios":"Calling `pipe.multi()` and then `await pipe.watch('k')` on the same pipeline. The watch() guard checks explicit_transaction and aborts.","commonSituations":"Reordering transaction setup so WATCH lands after MULTI; helper code that conditionally adds watches inside an already-open transaction.","solutions":["Issue all WATCH calls before multi(): `await pipe.watch('k'); pipe.multi()`.","Use `client.pipeline(transaction=True)` and call watch() before queuing commands (watch() runs immediately when not in explicit_transaction).","If you must change the watch set, DISCARD and start a new transaction."],"exampleFix":"// before\npipe = r.pipeline()\npipe.multi()\nawait pipe.watch('k')  # raises\n// after\npipe = r.pipeline()\nawait pipe.watch('k')\npipe.multi()\nawait pipe.set('k', 'v')\nawait pipe.execute()","handlingStrategy":"validation","validationCode":"pipe = r.pipeline()\nawait pipe.watch('k')   # WATCH before MULTI\nif getattr(pipe, 'explicit_transaction', False):\n    raise RuntimeError('cannot WATCH after MULTI')\npipe.multi()\nawait pipe.set('k', 'v')\nawait pipe.execute()","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always WATCH before multi().","If the watch set must change, DISCARD and start over.","Use client.pipeline(transaction=True) and call watch() first."],"tags":["redis","asyncio","pipeline","transactions","watch","api-misuse"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}