redis/redis-py · error · ResponseError
Wrong number of response items from pipeline execution
Error message
Wrong number of response items from pipeline execution
What it means
Raised as ResponseError from _execute_transaction when the number of items in the EXEC response does not equal the number of queued commands. This indicates the client and server disagree on the transaction contents — a protocol-level corruption or a server-side abort that truncated results. The connection is forcibly disconnected because its state is now unreliable.
Solutions
- Do not reuse a pipeline after a failure — create a fresh one each transaction.
- Bypass any intermediary proxy that does not faithfully forward MULTI/EXEC framing; connect directly to a Redis node.
- Verify Redis and redis-py versions are compatible for the commands in the stack.
- Capture the command stack and response lengths (logged in the error path) to identify the divergent command, and file a bug if reproducible against a real Redis.
- Ensure no custom command mutates the response list unexpectedly.
Example fix
// before (reused pipeline after error)
pipe = r.pipeline(transaction=True)
pipe.set('a', 1)
try:
pipe.execute()
except Exception:
pass
pipe.set('b', 2)
pipe.execute() # state may be inconsistent -> ResponseError
// after (fresh pipeline per transaction)
with r.pipeline(transaction=True) as pipe:
pipe.set('a', 1)
pipe.execute() Defensive patterns
Strategy: try-catch
Validate before calling
# Pre-check: ensure the command stack matches what you intend and the pipeline is fresh.
assert not pipe.connection or not getattr(pipe.connection, '_desynced', False), \
'pipeline/connection previously failed; create a new pipeline' Type guard
def pipeline_is_fresh(pipe) -> bool:
return len(pipe.command_stack) == 0 and pipe.connection is None Try / catch
from redis.exceptions import ResponseError
try:
pipe.execute()
except ResponseError as e:
if 'Wrong number of response items' in str(e):
# connection was disconnected by the library; start a fresh pipeline
pipe = r.pipeline(transaction=True)
else:
raise Prevention
- Never reuse a pipeline after any failure; create a new one.
- Avoid proxies that rewrite MULTI/EXEC framing; connect directly to Redis.
- Keep Redis and redis-py versions aligned for the commands you queue.
When it happens
Trigger: The command stack length and the EXEC reply length diverge. Causes: a command in the stack was rejected at queue time but still counted; RESP framing corruption from a proxy/intermediary; mixed RESP2/RESP3 oddities; bugs in custom response callbacks that mutate the response list.
Common situations: A Redis proxy (Twemproxy, Redis Cluster proxy, Envoy) that rewrites MULTI/EXEC; corrupted connections from a load balancer; running an old redis-py against a newer Redis with changed command arity; pipeline reuse after a partial failure.
Related errors
- Unexpected response length for cluster pipeline EXEC…
- Wrong number of response items from pipeline execution
- A occurred while watching one or more keys
- A occurred while watching one or more keys
- All keys involved in a cluster transaction must map to the…
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/f636f0441187549b.
Report an issue: GitHub.
Appendix: source
Thrown at redis/client.py:2069
response = self.parse_response(connection, "_")
except ExecAbortError:
if errors:
raise errors[0][1]
raise
# EXEC clears any watched keys
self.watching = False
if response is None:
raise WatchError("Watched variable changed.")
# put any parse errors into the response
for i, e in errors:
response.insert(i, e)
if len(response) != len(commands):
self.connection.disconnect()
raise ResponseError(
"Wrong number of response items from pipeline execution"
)
# find any errors in the response and raise if necessary
if raise_on_error:
self.raise_first_error(commands, response)
# We have to run response callbacks manually
data = []
for r, cmd in zip(response, commands):
if not isinstance(r, Exception):
args, options = cmd
# Remove keys entry, it needs only for cache.
options.pop("keys", None)
command_name = args[0]
if command_name in self.response_callbacks:
r = self.response_callbacks[command_name](r, **options)
data.append(r)View on GitHub (pinned to 6a6b581b48)