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

Raised unconditionally by PipelineStrategy.watch() — the non-transactional cluster pipeline strategy. WATCH is a Redis optimistic-locking primitive that only makes sense inside a MULTI/EXEC transaction; a non-transactional cluster pipeline has no transactional context, so watch() is not supported there.

Source

Thrown at redis/asyncio/cluster.py:3037

                        if type(cmd.result) in RedisCluster.ERRORS_ALLOW_RETRY:
                            client.replace_default_node()
                            break

        return [cmd.result for cmd in stack]

    async def reset(self):
        """
        Reset back to empty pipeline.
        """
        self._command_queue = []

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

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

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

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

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

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Use a transactional pipeline: pipe = rc.pipeline(transaction=True), then await pipe.watch('key').
  2. If optimistic locking isn't needed, remove the watch() call.
  3. Prefer the cluster lock (redis.lock.Lock) over WATCH/MULTI for distributed mutual exclusion.

Example fix

// before
pipe = rc.pipeline()
await pipe.watch('counter')  # raises

// after
pipe = rc.pipeline(transaction=True)
await pipe.watch('counter')
Defensive patterns

Strategy: validation

Validate before calling

def watch_if_transactional(pipe, *keys):
    if not getattr(pipe, '_transaction', False):
        raise ValueError('watch() requires rc.pipeline(transaction=True)')
    return pipe.watch(*keys)

Type guard

from redis.asyncio.cluster import TransactionStrategy

def supports_watch(pipe) -> bool:
    return isinstance(getattr(pipe, '_strategy', None), TransactionStrategy)

Try / catch

from redis.cluster import RedisClusterException

try:
    await pipe.watch('k')
except RedisClusterException as e:
    if 'watch()' in str(e):
        pipe = rc.pipeline(transaction=True)
        await pipe.watch('k')
    else:
        raise

Prevention

When it happens

Trigger: Calling await pipe.watch('key') on a pipeline created via rc.pipeline() or rc.pipeline(transaction=False). Transactional pipelines (rc.pipeline(transaction=True)) support WATCH.

Common situations: Porting standalone optimistic-locking code to cluster mode without enabling the transactional pipeline; calling watch() for the first time on a freshly created non-transactional pipe.

Related errors


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