redis/redis-py · error · RedisError
Commands without an initial WATCH have already been issued
Error message
Commands without an initial WATCH have already been issued
What it means
Raised by Pipeline.multi() when self.command_stack is non-empty — meaning commands were already queued before MULTI. In redis-py's transaction model you must call WATCH (and multi()) before queuing any commands; queuing commands first then calling multi() indicates the user meant to use transaction=True implicitly or mis-ordered their calls.
Solutions
- Call pipe.multi() BEFORE queuing any commands (after optional WATCH).
- Or construct the pipeline with transaction=True and let execute() handle MULTI/EXEC automatically — then you never call multi() yourself.
- Reset the pipeline (pipe.reset()) and reorder if you queued commands prematurely.
Example fix
// before
pipe = r.pipeline()
pipe.set('k', 'v')
pipe.multi() # RedisError
// after (explicit)
pipe = r.pipeline()
pipe.multi()
pipe.set('k', 'v')
pipe.execute()
// after (implicit, preferred)
pipe = r.pipeline(transaction=True)
pipe.set('k', 'v')
pipe.execute() Defensive patterns
Strategy: validation
Validate before calling
if pipe.command_stack:
raise RuntimeError('Commands already queued; call multi() before queuing')
pipe.multi() Type guard
def pipeline_ready_for_multi(pipe) -> bool:
return not pipe.command_stack and not pipe.explicit_transaction Try / catch
from redis.exceptions import RedisError
try:
pipe.multi()
except RedisError as e:
if 'without an initial WATCH' in str(e):
pipe.reset()
pipe.multi() # retry in correct order
else:
raise Prevention
- Order: WATCH → read → multi() → queue → execute().
- Use pipeline(transaction=True) to let the library manage MULTI placement.
- Reset the pipeline if you queued commands before multi().
When it happens
Trigger: Calling pipe.set('k','v') (or any command) before pipe.multi(). The presence of entries in command_stack trips the guard at client.py:1890.
Common situations: Misunderstanding the manual transaction API order (multi must precede command queuing); migrating from pipeline(transaction=True) auto-mode to explicit multi() and getting the order wrong.
Related errors
- Cannot issue a WATCH after a MULTI
- Cannot issue a WATCH after a MULTI
- Cannot issue nested calls to MULTI
- method discard() is not supported outside of transactional…
- method multi() is not supported outside of transactional…
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/4547849dce7bfaa7.
Report an issue: GitHub.
Appendix: source
Thrown at redis/client.py:1891
# we can safely return the connection to the pool here since we're
# sure we're no longer WATCHing anything
if self.connection:
self.connection_pool.release(self.connection)
self.connection = None
def close(self) -> None:
"""Close the pipeline"""
self.reset()
def multi(self) -> None:
"""
Start a transactional block of the pipeline after WATCH commands
are issued. End the transactional block with `execute`.
"""
if self.explicit_transaction:
raise RedisError("Cannot issue nested calls to MULTI")
if self.command_stack:
raise RedisError(
"Commands without an initial WATCH have already been issued"
)
self.explicit_transaction = True
def execute_command(self, *args, **kwargs):
if (self.watching or args[0] == "WATCH") and not self.explicit_transaction:
return self.immediate_execute_command(*args, **kwargs)
return self.pipeline_execute_command(*args, **kwargs)
def _disconnect_reset_raise_on_watching(
self,
conn: AbstractConnection,
error: Exception,
failure_count: Optional[int] = None,
start_time: Optional[float] = None,
command_name: Optional[str] = None,
) -> None:
"""View on GitHub (pinned to 6a6b581b48)