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 by _validate_watch (as the base RedisError) when WATCH is issued after MULTI has already started an explicit transaction. In Redis, WATCH after MULTI is illegal — keys must be watched before MULTI begins — so the library mirrors that rule on the client side.

Source

Thrown at redis/asyncio/cluster.py:3172

                    )

                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 super().execute_command(*args, **kwargs)

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

        self._watching = True

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

    async def _get_connection_and_send_command(self, *args, **options):
        redis_node, connection = self._get_client_and_connection_for_transaction()
        # Only disconnect if not watching - disconnecting would lose WATCH state
        if not self._watching:
            await redis_node.disconnect_if_needed(connection)

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

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Call watch() before multi(): WATCH the keys first, then start the transaction with multi().
  2. Restructure dynamic pipelines so all WATCH calls are decided and issued before MULTI.
  3. If you no longer need to watch, call pipe.unwatch() (or reset the pipeline) instead of adding a new WATCH mid-transaction.

Example fix

// before
await pipe.multi()
await pipe.watch('k')  # raises [83]
// after
await pipe.watch('k')
await pipe.multi()
Defensive patterns

Strategy: validation

Validate before calling

if pipe._explicit_transaction:
    raise ValueError('WATCH must be issued before MULTI')

Try / catch

from redis.exceptions import RedisError
try:
    await pipe.watch('k')
except RedisError as e:
    if 'Cannot issue a WATCH after a MULTI' in str(e):
        pipe.reset(); await pipe.watch('k')  # restart in correct order

Prevention

When it happens

Trigger: Calling pipe.multi() (which sets _explicit_transaction) and then pipe.watch('k') on a cluster pipeline; any code path that issues WATCH once the explicit MULTI flag is set.

Common situations: Porting standalone-pipeline code that interleaves WATCH and MULTI in the wrong order; building a pipeline dynamically where WATCH is conditionally added after MULTI was already begun.

Related errors


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