redis/redis-py · critical · InvalidPipelineStack

Unexpected response length for cluster pipeline EXEC…

Error message

Unexpected response length for cluster pipeline EXEC. Command stack was {} but response had length {}

What it means

InvalidPipelineStack raised in TransactionStrategy._execute_transaction (redis/asyncio/cluster.py:3462) when the EXEC response array length does not equal the buffered command queue length. This indicates a protocol desynchronization between what was queued and what the server returned - typically an internal library bug, a connection that silently dropped a command, or mixed protocol/server behavior. It should not occur in normal use.

Solutions

  1. Ensure a ClusterPipeline is not shared/used concurrently across asyncio tasks
  2. Upgrade redis-py to the latest patch release - this is usually an internal bug
  3. Reproduce with debug logging and file an issue with the command stack from the error message
  4. Reset the pipeline and retry the transaction from scratch
Defensive patterns

Strategy: try-catch

Validate before calling

# internal invariant; cannot be prevented by callers. Avoid sharing a pipeline across tasks.

Try / catch

try:
    await pipe.execute()
except InvalidPipelineStack:
    await pipe.reset()
    # rebuild and retry once; if it recurs, upgrade redis-py and report

Prevention

When it happens

Trigger: A corruption of the pipeline command queue; a RESP framing desync; an unexpected server-side abort that changed the response count; library version mismatch with server behavior.

Common situations: Hitting an edge case after a reconnect mid-transaction; using commands whose response shape the library mis-counts; concurrency bugs sharing a pipeline across tasks.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/54c241aacffd65da. Report an issue: GitHub.

Appendix: source

Thrown at redis/asyncio/cluster.py:3462

        except ExecAbortError:
            if errors:
                raise errors[0]
            raise

        self._executing = False

        # 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(self._command_queue):
            raise InvalidPipelineStack(
                "Unexpected response length for cluster pipeline EXEC."
                " Command stack was {} but response had length {}".format(
                    [c.args[0] for c in self._command_queue], len(response)
                )
            )

        # find any errors in the response and raise if necessary
        if raise_on_error or len(errors) > 0:
            await self._raise_first_error(
                response,
                self._command_queue,
                start_time,
            )

        # We have to run response callbacks manually
        data = []
        for r, cmd in zip(response, self._command_queue):
            if not isinstance(r, Exception):

View on GitHub (pinned to 6a6b581b48)