{"record":{"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/6a6b581b48225afa0b76912d1028c6035baee932/redis/asyncio/client.py#L2282-L2306","documentation":"Raised by Pipeline.watch() when self.explicit_transaction is True — i.e. MULTI has already been issued on this connection. Redis forbids WATCH after MULTI (WATCH must precede MULTI); the library enforces the ordering client-side.","triggerScenarios":"Calling pipe.multi() then await pipe.watch('k'); or issuing WATCH inside the MULTI/EXEC body by mistake.","commonSituations":"Reordering WATCH and MULTI during refactoring; conditionally adding WATCH inside a block that already started a transaction.","solutions":["Move all WATCH calls before multi(): WATCH -> (optional reads) -> multi() -> commands -> execute().","If you no longer need the watch, unwatch() before multi(); never watch() after."],"exampleFix":"// before\npipe.multi()\nawait pipe.set('a', 1)\nawait pipe.watch('a')\n// after\nawait pipe.watch('a')\npipe.multi()\nawait pipe.set('a', 1)","handlingStrategy":"validation","validationCode":"assert not pipe.explicit_transaction, 'cannot WATCH after MULTI'\nawait pipe.watch('k')","typeGuard":"def can_watch(pipe) -> bool:\n    return not pipe.explicit_transaction","tryCatchPattern":"try:\n    await pipe.watch('k')\nexcept RedisError as e:\n    if 'WATCH after a MULTI' in str(e):\n        # discard, restructure: WATCH first, then multi()\n        await pipe.discard()\n    else:\n        raise","preventionTips":["Always issue WATCH before multi().","Use the context-manager pipeline to keep ordering correct."],"tags":["pipeline","transaction","watch","ordering","multi"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}