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 observabilityView on GitHub (pinned to da03cdc7e8)
Solutions
- Treat WatchError as a retry signal: re-WATCH, re-queue, and re-execute the transaction in a loop.
- Increase retry count / backoff so transient errors are absorbed before reaching this handler.
- 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
- Treat WatchError as a retry trigger, not a fatal error.
- Configure retry/backoff to absorb transient connection errors before they reach the watch handler.
- Keep the WATCH-to-EXEC window short.
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
- Watched variable changed.
- Cannot issue a WATCH after a MULTI
- Cannot issue nested calls to MULTI
- Commands without an initial WATCH have already been issued
- Wrong number of response items from pipeline execution
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/602f64df4fb77554.json.
Report an issue: GitHub.