redis/redis-py · error · RedisClusterException

method watch() is not supported outside of transactional con

Error message

method watch() is not supported outside of transactional context

What it means

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.

Source

Thrown at redis/cluster.py:4535

            nodes = policy_callback()

        if args[0].lower() == "ft.aggregate":
            self._aggregate_nodes = nodes

        return nodes

    def multi(self):
        raise RedisClusterException(
            "method multi() is not supported outside of transactional context"
        )

    def discard(self):
        raise RedisClusterException(
            "method discard() is not supported outside of transactional context"
        )

    def watch(self, *names):
        raise RedisClusterException(
            "method watch() is not supported outside of transactional context"
        )

    def unwatch(self, *names):
        raise RedisClusterException(
            "method unwatch() is not supported outside of transactional context"
        )

    def delete(self, *names):
        if len(names) != 1:
            raise RedisClusterException(
                "deleting multiple keys is not implemented in pipeline command"
            )

        return self.execute_command("DEL", names[0])

    def unlink(self, *names):
        if len(names) != 1:

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Use a transactional pipeline for WATCH: rc.pipeline(transaction=True), and ensure all watched keys share a hash slot.
  2. If you must run transaction=False, drop WATCH and implement optimistic locking another way (e.g. compare-and-set via Lua on the client).
  3. Branch code on the pipeline mode so watch() is only invoked transactionally.

Example fix

# before
pipe = rc.pipeline(transaction=False)
pipe.watch('user:1')  # raises

# after
pipe = rc.pipeline(transaction=True)
pipe.watch('user:1')
pipe.multi()
pipe.set('user:1', 'alice')
pipe.execute()
Defensive patterns

Strategy: validation

Validate before calling

def watch_keys(client, *keys, transaction=True):
    if not transaction:
        raise ValueError('WATCH requires a transactional pipeline (transaction=True)')
    pipe = client.pipeline(transaction=True)
    pipe.watch(*keys)
    return pipe

Try / catch

from redis.exceptions import RedisClusterException

try:
    pipe.watch('k')
except RedisClusterException:
    pipe = rc.pipeline(transaction=True)
    pipe.watch('k')

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/a2400dd8b38b0853.json. Report an issue: GitHub.