{"record":{"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":4557,"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":4539,"sourceCodeEnd":4575,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/cluster.py#L4539-L4575","documentation":"Raised by AbstractStrategy.watch() in redis/cluster.py:4557. WATCH is a conditional-transaction primitive that must be sent on the connection owning the watched key's slot; on a non-transactional cluster pipeline there is no pinned connection, so watch() is rejected. Only the TransactionStrategy supports WATCH (via _validate_watch / immediate execution).","triggerScenarios":"Calling pipe.watch('key') on a ClusterPipeline opened without transaction=True, or calling watch() before the transactional context is established.","commonSituations":"Porting optimistic-locking code (WATCH/MULTI/EXEC) from standalone Redis to cluster; assuming watch works on the default pipeline.","solutions":["Use a transactional pipeline: with rc.pipeline(transaction=True) as pipe: pipe.watch('k'); pipe.multi(); ...","Ensure all watched keys hash to the same slot (use hash tags {tag}key1, {tag}key2).","If you only need conditional execution without atomicity, reconsider whether WATCH is necessary."],"exampleFix":"// before\npipe = rc.pipeline()\npipe.watch('k')\n// after\nwith rc.pipeline(transaction=True) as pipe:\n    pipe.watch('k')\n    val = pipe.get('k').execute()[0]\n    pipe.multi()\n    pipe.set('k', newval)\n    pipe.execute()","handlingStrategy":"validation","validationCode":"with rc.pipeline(transaction=True) as pipe:\n    pipe.watch('k')  # OK in transactional pipeline","typeGuard":"def is_transactional_pipe(pipe) -> bool:\n    return bool(getattr(pipe, 'transaction', False))","tryCatchPattern":"from redis.exceptions import RedisClusterException\ntry:\n    pipe.watch('k')\nexcept RedisClusterException:\n    with rc.pipeline(transaction=True) as tpipe:\n        tpipe.watch('k')","preventionTips":["Always use transaction=True when you need WATCH.","Use hash tags so all watched keys share a slot."],"tags":["cluster","pipeline","transaction","optimistic-locking","api-misuse"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}