redis/redis-py · error · RedisClusterException

method unwatch() is not supported outside of transactional c

Error message

method unwatch() is not supported outside of transactional context

What it means

Raised unconditionally by PipelineStrategy.unwatch() — the non-transactional cluster pipeline strategy. UNWATCH clears keys watched with WATCH; since a non-transactional cluster pipeline never supported WATCH in the first place, UNWATCH has no effect and is rejected to surface the misuse.

Source

Thrown at redis/asyncio/cluster.py:3042

    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"
            )

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


class TransactionStrategy(AbstractStrategy):

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Switch to a transactional pipeline: pipe = rc.pipeline(transaction=True), then await pipe.unwatch().
  2. If you are not using WATCH, remove the unwatch() call entirely.
  3. Restructure so transactional-cleanup calls only run when transaction=True was used.

Example fix

// before
pipe = rc.pipeline()
await pipe.unwatch()  # raises

// after
pipe = rc.pipeline(transaction=True)
await pipe.unwatch()
Defensive patterns

Strategy: validation

Validate before calling

async def safe_unwatch(pipe):
    if not getattr(pipe, '_transaction', False):
        return  # nothing to unwatch on a non-transactional pipeline
    await pipe.unwatch()

Type guard

from redis.asyncio.cluster import TransactionStrategy

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

Try / catch

from redis.cluster import RedisClusterException

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

Prevention

When it happens

Trigger: Calling await pipe.unwatch() on a pipeline created via rc.pipeline() or rc.pipeline(transaction=False). Only transactional pipelines (transaction=True) implement unwatch().

Common situations: Porting standalone Redis pipeline cleanup code (try/finally with unwatch()) to cluster mode without the transactional pipeline; calling unwatch() defensively on the wrong pipeline flavor.

Related errors


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