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 command_stack is already non-empty but no WATCH preceded it. The intended workflow is WATCH -> MULTI -> queued commands -> EXEC; issuing commands first (buffered pipeline) and then calling multi() would mix a non-transactional pipeline with a transaction.
Solutions
- Order your calls WATCH (if any) -> multi() -> queued commands -> execute().
- If you did not intend a transaction, omit multi() and just call execute() on the buffered pipeline.
- Use the context-manager pipeline which enforces the correct order.
Example fix
// before
pipe = client.pipeline(transaction=True)
await pipe.set('a', 1)
pipe.multi()
// after
pipe = client.pipeline(transaction=True)
pipe.multi()
await pipe.set('a', 1)
await pipe.execute() Defensive patterns
Strategy: validation
Validate before calling
assert not pipe.command_stack, 'issue WATCH before any commands, then multi()' pipe.multi()
Type guard
def ready_for_multi(pipe) -> bool:
return not pipe.command_stack Try / catch
try:
pipe.multi()
except RedisError as e:
if 'without an initial WATCH' in str(e):
# recreate pipeline and issue WATCH -> multi() in order
... Prevention
- Always WATCH (if needed) then multi() before queuing commands.
- Use the context-manager pipeline to enforce ordering.
When it happens
Trigger: Calling pipe.set('a',1); pipe.multi() — i.e. queuing commands before MULTI without a prior WATCH. The library refuses because the queued commands cannot be turned into a transaction retroactively.
Common situations: Misunderstanding the pipeline model: assuming multi() can be called after queuing; refactoring that moves multi() below the command calls.
Related errors
- Cannot issue a WATCH after a MULTI
- All keys involved in a cluster transaction must map to the…
- Cannot issue a WATCH after a MULTI
- Cannot issue nested calls to MULTI
- method multi() is not supported outside of transactional…
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/92ddfc6fce316a8b.
Report an issue: GitHub.
Appendix: source
Thrown at redis/asyncio/client.py:1897
# release itself so a second cancel cannot split the pool's
# internal in-use/available bookkeeping mid-update.
if self.connection:
connection, self.connection = self.connection, None
await asyncio.shield(self.connection_pool.release(connection))
async def aclose(self) -> None:
"""Alias for reset(), a standard method name for cleanup"""
await self.reset()
def multi(self):
"""
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
) -> Union["Pipeline", Awaitable["Pipeline"]]:
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)
async def _disconnect_reset_raise_on_watching(
self,
conn: Connection,
error: Exception,
failure_count: Optional[int] = None,
start_time: Optional[float] = None,
command_name: Optional[str] = None,View on GitHub (pinned to 6a6b581b48)