redis/redis-py · error · RedisError

Cannot issue a WATCH after a MULTI

Error message

Cannot issue a WATCH after a MULTI

What it means

Raised in TransactionStrategy._validate_watch (redis/cluster.py:4651) as RedisError when WATCH is called after MULTI has already started an explicit transaction. Per Redis semantics, WATCH must be issued before MULTI; once MULTI begins, the watched-keys set is frozen until EXEC/UNWATCH.

Source

Thrown at redis/cluster.py:4651

                    )

                self._pipeline_slots.add(slot_number)
            elif args[0] not in self.NO_SLOTS_COMMANDS:
                raise RedisClusterException(
                    f"Cannot identify slot number for command: {args[0]},"
                    "it cannot be triggered in a transaction"
                )

            return self._immediate_execute_command(*args, **kwargs)
        else:
            if slot_number is not None:
                self._pipeline_slots.add(slot_number)

            return self.pipeline_execute_command(*args, **kwargs)

    def _validate_watch(self):
        if self._explicit_transaction:
            raise RedisError("Cannot issue a WATCH after a MULTI")

        self._watching = True

    def _immediate_execute_command(self, *args, **options):
        return self._retry.call_with_retry(
            lambda: self._get_connection_and_send_command(*args, **options),
            self._reinitialize_on_error,
            with_failure_count=True,
        )

    def _get_connection_and_send_command(self, *args, **options):
        redis_node, connection = self._get_client_and_connection_for_transaction()

        # Start timing for observability
        start_time = time.monotonic()

        try:
            response = self._send_command_parse_response(

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Issue WATCH before MULTI: pipe.watch('k'); val = pipe.get(...); pipe.multi(); pipe.set(...); pipe.execute().
  2. If you must change watched keys, UNWATCH (or EXEC) first, then re-WATCH before a new MULTI.
  3. Review the order of operations: every WATCH must precede the MULTI that opens the transaction.

Example fix

# before
pipe = rc.pipeline(transaction=True)
pipe.multi()
pipe.watch('k')   # raises: Cannot issue a WATCH after a MULTI

# after
pipe = rc.pipeline(transaction=True)
pipe.watch('k')    # WATCH BEFORE MULTI
pipe.multi()
pipe.set('k', 'v')
pipe.execute()
Defensive patterns

Strategy: validation

Validate before calling

class TxBuilder:
    def __init__(self, client):
        self.pipe = client.pipeline(transaction=True)
        self._multi_started = False
    def watch(self, *keys):
        if self._multi_started:
            raise RuntimeError('WATCH after MULTI is not allowed')
        self.pipe.watch(*keys)
    def multi(self):
        self._multi_started = True
        self.pipe.multi()

Try / catch

from redis.exceptions import RedisError

try:
    pipe.watch('k')
except RedisError as e:
    if 'WATCH after a MULTI' in str(e):
        pipe.unwatch()
        pipe.watch('k')
    else:
        raise

Prevention

When it happens

Trigger: pipe.multi(); pipe.watch('k'). The _explicit_transaction flag is True when multi() ran, so the subsequent WATCH call hits _validate_watch and raises.

Common situations: Reordering optimistic-locking code so MULTI precedes WATCH. Copy-pasting transaction bodies that append WATCH calls. Race-free intent gone wrong: calling MULTI early then trying to add watches.

Related errors


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