{"id":"a2400dd8b38b0853","repo":"redis/redis-py","slug":"method-watch-is-not-supported-outside-of-transac-a2400d","errorCode":null,"errorMessage":"method watch() is not supported outside of transactional context","messagePattern":"method watch\\(\\) is not supported outside of transactional context","errorType":"exception","errorClass":"RedisClusterException","httpStatus":null,"severity":"error","filePath":"redis/cluster.py","lineNumber":4535,"sourceCode":"            nodes = policy_callback()\n\n        if args[0].lower() == \"ft.aggregate\":\n            self._aggregate_nodes = nodes\n\n        return nodes\n\n    def multi(self):\n        raise RedisClusterException(\n            \"method multi() is not supported outside of transactional context\"\n        )\n\n    def discard(self):\n        raise RedisClusterException(\n            \"method discard() is not supported outside of transactional context\"\n        )\n\n    def watch(self, *names):\n        raise RedisClusterException(\n            \"method watch() is not supported outside of transactional context\"\n        )\n\n    def unwatch(self, *names):\n        raise RedisClusterException(\n            \"method unwatch() is not supported outside of transactional context\"\n        )\n\n    def delete(self, *names):\n        if len(names) != 1:\n            raise RedisClusterException(\n                \"deleting multiple keys is not implemented in pipeline command\"\n            )\n\n        return self.execute_command(\"DEL\", names[0])\n\n    def unlink(self, *names):\n        if len(names) != 1:","sourceCodeStart":4517,"sourceCodeEnd":4553,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/cluster.py#L4517-L4553","documentation":"PipelineStrategy.watch() (redis/cluster.py:4534) always raises. WATCH provides optimistic locking for a MULTI/EXEC transaction; it is meaningless on a non-transactional pipeline (transaction=False), which is why the non-transactional strategy rejects it.","triggerScenarios":"Calling pipe.watch('key') on a pipeline created with rc.pipeline(transaction=False). The call routes ClusterPipeline.watch (redis/cluster.py:3734) -> PipelineStrategy.watch -> raises at redis/cluster.py:4535.","commonSituations":"Porting standalone optimistic-locking code (WATCH/MULTI/EXEC) into a cluster app while forcing transaction=False. Shared pipeline helper used by both batched and transactional flows.","solutions":["Use a transactional pipeline for WATCH: rc.pipeline(transaction=True), and ensure all watched keys share a hash slot.","If you must run transaction=False, drop WATCH and implement optimistic locking another way (e.g. compare-and-set via Lua on the client).","Branch code on the pipeline mode so watch() is only invoked transactionally."],"exampleFix":"# before\npipe = rc.pipeline(transaction=False)\npipe.watch('user:1')  # raises\n\n# after\npipe = rc.pipeline(transaction=True)\npipe.watch('user:1')\npipe.multi()\npipe.set('user:1', 'alice')\npipe.execute()","handlingStrategy":"validation","validationCode":"def watch_keys(client, *keys, transaction=True):\n    if not transaction:\n        raise ValueError('WATCH requires a transactional pipeline (transaction=True)')\n    pipe = client.pipeline(transaction=True)\n    pipe.watch(*keys)\n    return pipe","typeGuard":null,"tryCatchPattern":"from redis.exceptions import RedisClusterException\n\ntry:\n    pipe.watch('k')\nexcept RedisClusterException:\n    pipe = rc.pipeline(transaction=True)\n    pipe.watch('k')","preventionTips":["Always create the pipeline with transaction=True before WATCH.","Ensure all watched keys share a hash tag so they fall in one slot.","Avoid transaction=False for any optimistic-locking code path."],"tags":["cluster","pipeline","transaction","optimistic-locking"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}