redis/redis-py · warning · WatchError

A {type(error).__name__} occurred while watching one or more

Error message

A {type(error).__name__} occurred while watching one or more keys

What it means

Raised by Pipeline._disconnect_reset_raise_on_watching (redis/asyncio/client.py:1947) when a connection error occurs during a WATCH-protected transaction. Because the connection died, any WATCH is invalidated; the library disconnects, resets watching state, and raises WatchError wrapping the underlying error type name so the caller knows to retry the whole transaction.

Source

Thrown at redis/asyncio/client.py:1947

            and failure_count is not None
            and failure_count <= conn.retry.get_retries()
        ):
            await record_operation_duration(
                command_name=command_name,
                duration_seconds=time.monotonic() - start_time,
                server_address=getattr(conn, "host", None),
                server_port=getattr(conn, "port", None),
                db_namespace=str(conn.db),
                error=error,
                retry_attempts=failure_count,
            )
        await conn.disconnect(error=error, failure_count=failure_count)
        # if we were already watching a variable, the watch is no longer
        # valid since this connection has died. raise a WatchError, which
        # indicates the user should retry this transaction.
        if self.watching:
            await self.reset()
            raise WatchError(
                f"A {type(error).__name__} occurred while watching one or more keys"
            )

    async def immediate_execute_command(self, *args, **options):
        """
        Execute a command immediately, but don't auto-retry on the supported
        errors for retry if we're already WATCHing a variable.
        Used when issuing WATCH or subsequent commands retrieving their values but before
        MULTI is called.
        """
        command_name = args[0]
        conn = self.connection
        # if this is the first call, we need a connection
        if not conn:
            conn = await self.connection_pool.get_connection()
            self.connection = conn

        # Start timing for observability

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Treat WatchError as a retry signal: re-WATCH, re-queue, and re-execute the transaction in a loop.
  2. Increase retry count / backoff so transient errors are absorbed before reaching this handler.
  3. Shorten the WATCH-to-EXEC window to reduce exposure to connection drops.

Example fix

// before
pipe = r.pipeline(transaction=True)
await pipe.watch('k')
await pipe.multi()
await pipe.set('k', 'v')
await pipe.execute()
// after
for _ in range(MAX_RETRIES):
    try:
        pipe = r.pipeline(transaction=True)
        await pipe.watch('k')
        await pipe.multi()
        await pipe.set('k', 'v')
        await pipe.execute()
        break
    except redis.exceptions.WatchError:
        continue
Defensive patterns

Strategy: retry

Try / catch

from redis.exceptions import WatchError
for _ in range(MAX_RETRIES):
    try:
        pipe = r.pipeline(transaction=True)
        await pipe.watch('k')
        await pipe.multi()
        await pipe.set('k', 'v')
        await pipe.execute()
        break
    except WatchError:
        continue

Prevention

When it happens

Trigger: A network error, timeout, or reconnectable server condition (the retry-supported errors) exhausts retries while self.watching is True inside a pipeline transaction. The failure_callback in immediate_execute_command routes here.

Common situations: Transient network blips or server failovers during an optimistic-locking transaction; socket timeouts under load while WATCH is active.

Related errors


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