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

  1. Do not reuse a pipeline after a failure — create a fresh one each transaction.
  2. Bypass any intermediary proxy that does not faithfully forward MULTI/EXEC framing; connect directly to a Redis node.
  3. Verify Redis and redis-py versions are compatible for the commands in the stack.
  4. 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.
  5. 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

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


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)