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 Pipeline.watch() when self.explicit_transaction is True — i.e. WATCH is called after MULTI has already started. Redis protocol forbids this: WATCH must precede MULTI. The guard at client.py:2274 catches the misuse client-side before sending anything.

Solutions

  1. Call watch() BEFORE multi(): the correct order is WATCH → (read) → MULTI → (queue) → EXEC.
  2. Or use pipeline(transaction=True) and call watch() before execute() — auto-MULTI is issued internally at execute time, so watch() is always pre-MULTI.
  3. Reset the pipeline and reorder if you called multi() too early.

Example fix

// before
pipe = r.pipeline()
pipe.multi()
pipe.watch('k')  # RedisError: Cannot issue a WATCH after a MULTI

// after
pipe = r.pipeline()
pipe.watch('k')
v = pipe.get('k')
pipe.multi()
pipe.set('k', transform(v))
pipe.execute()
Defensive patterns

Strategy: validation

Validate before calling

if pipe.explicit_transaction:
    raise RuntimeError('Cannot WATCH after MULTI; call watch() before multi()')
pipe.watch('k')

Type guard

def pipeline_can_watch(pipe) -> bool:
    return not pipe.explicit_transaction

Try / catch

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

Prevention

When it happens

Trigger: Calling pipe.multi() then pipe.watch(...). The explicit_transaction flag is already True, so watch() raises immediately.

Common situations: Reordering transaction construction (calling multi before watch); helper functions that call watch unconditionally; refactoring code that previously used pipeline(transaction=True) auto-mode.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/6fc0887133e17e86. Report an issue: GitHub.

Appendix: source

Thrown at redis/client.py:2275

            )
            raise

        finally:
            # in reset() the connection is disconnected before returned to the pool if
            # it is marked for reconnect.
            self.reset()

    def discard(self):
        """
        Flushes all previously queued commands
        See: https://redis.io/commands/DISCARD
        """
        self.execute_command("DISCARD")

    def watch(self, *names):
        """Watches the values at keys ``names``"""
        if self.explicit_transaction:
            raise RedisError("Cannot issue a WATCH after a MULTI")
        return self.execute_command("WATCH", *names)

    def unwatch(self) -> bool:
        """Unwatches all previously specified keys"""
        return self.watching and self.execute_command("UNWATCH") or True

View on GitHub (pinned to 6a6b581b48)