redis/redis-py · error · WatchError
A occurred while watching one or more keys
Error message
A {type(error).__name__} occurred while watching one or more keys What it means
Raised as a WatchError from Pipeline._disconnect_reset_raise_on_watching() during immediate_execute_command — the path that runs WATCH and subsequent reads before MULTI. When the connection dies while WATCH is active, the watch is invalidated and the library raises WatchError so the caller retries the whole transaction. The error message embeds the underlying exception class name (e.g. ConnectionError, TimeoutError).
Solutions
- Wrap the WATCH + MULTI + EXEC sequence in a retry loop that catches redis.exceptions.WatchError and re-runs the whole transaction.
- Increase socket_timeout to survive transient slowness during the WATCH window.
- Investigate the underlying error class named in the message — fix the root connectivity issue (failover, OOM, firewall).
- Keep the WATCH→MULTI window short: read watched keys immediately, then enter MULTI.
Example fix
// before
pipe = r.pipeline()
pipe.watch('k')
v = pipe.get('k') # connection drops here -> WatchError
// after
from redis.exceptions import WatchError
for _ in range(retries):
try:
with r.pipeline() as pipe:
pipe.watch('k')
v = pipe.get('k')
pipe.multi()
pipe.set('k', new_val(v))
pipe.execute()
break
except WatchError:
continue Defensive patterns
Strategy: retry
Validate before calling
# No deterministic pre-check; instead keep the WATCH window short and verify connectivity.
if not pipe.connection or not pipe.connection.can_read(timeout=0):
pass # connection looks healthy enough to attempt WATCH Type guard
def pipeline_in_watch_window(pipe) -> bool:
return pipe.watching and not pipe.explicit_transaction Try / catch
from redis.exceptions import WatchError
for _ in range(retries):
try:
pipe = r.pipeline()
pipe.watch('k')
v = pipe.get('k')
pipe.multi()
pipe.set('k', transform(v))
pipe.execute()
break
except WatchError:
continue Prevention
- Always wrap WATCH-based transactions in a WatchError retry loop.
- Keep the WATCH→MULTI window minimal to reduce exposure to disconnects.
- Tune socket_timeout and retry/backoff to absorb transient failures.
When it happens
Trigger: A network failure, timeout, or server-initiated disconnect occurs while the pipeline is between WATCH and MULTI (i.e. self.watching is True). The failure_callback routes through _disconnect_reset_raise_on_watching which resets state and raises WatchError.
Common situations: Unstable network to Redis; Redis failover (sentinel/cluster) mid-transaction; socket_timeout too aggressive for the WATCH+read window; OOM or max-clients on the server dropping the connection.
Related errors
- A occurred while watching one or more keys
- Slot rebalancing occurred while watching keys
- Cannot identify slot number for command
- Cannot issue a WATCH after a MULTI
- Cannot issue a WATCH after a MULTI
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/6a35164693400796.
Report an issue: GitHub.
Appendix: source
Thrown at redis/client.py:1936
"""
if error and failure_count <= conn.retry.get_retries():
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,
)
conn.disconnect()
# 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:
self.reset()
raise WatchError(
f"A {type(error).__name__} occurred while watching one or more keys"
)
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 = self.connection_pool.get_connection()
self.connection = conn
# Start timing for observabilityView on GitHub (pinned to 6a6b581b48)