{"id":"54c241aacffd65da","repo":"redis/redis-py","slug":"unexpected-response-length-for-cluster-pipeline-ex","errorCode":null,"errorMessage":"Unexpected response length for cluster pipeline EXEC. Command stack was {} but response had length {}","messagePattern":"Unexpected response length for cluster pipeline EXEC\\. Command stack was (.+?) but response had length (.+?)","errorType":"exception","errorClass":"InvalidPipelineStack","httpStatus":null,"severity":"error","filePath":"redis/asyncio/cluster.py","lineNumber":3430,"sourceCode":"        except ExecAbortError:\n            if errors:\n                raise errors[0]\n            raise\n\n        self._executing = False\n\n        # EXEC clears any watched keys\n        self._watching = False\n\n        if response is None:\n            raise WatchError(\"Watched variable changed.\")\n\n        # put any parse errors into the response\n        for i, e in errors:\n            response.insert(i, e)\n\n        if len(response) != len(self._command_queue):\n            raise InvalidPipelineStack(\n                \"Unexpected response length for cluster pipeline EXEC.\"\n                \" Command stack was {} but response had length {}\".format(\n                    [c.args[0] for c in self._command_queue], len(response)\n                )\n            )\n\n        # find any errors in the response and raise if necessary\n        if raise_on_error or len(errors) > 0:\n            await self._raise_first_error(\n                response,\n                self._command_queue,\n                start_time,\n            )\n\n        # We have to run response callbacks manually\n        data = []\n        for r, cmd in zip(response, self._command_queue):\n            if not isinstance(r, Exception):","sourceCodeStart":3412,"sourceCodeEnd":3448,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/cluster.py#L3412-L3448","documentation":"Raised as InvalidPipelineStack after EXEC when len(response) != len(self._command_queue). The EXEC reply must contain exactly one entry per queued command; a length mismatch means the protocol stream is desynchronized (a command produced an empty/extra reply, or a response was miscounted). This is an internal-consistency assertion that usually indicates a library or server-side protocol bug rather than user data.","triggerScenarios":"Executing a cluster pipeline transaction where some queued command has EMPTY_RESPONSE kwargs that interact unexpectedly, or where a response callback / parser dropped or added an entry; also observable after server-side aborts that return a partial EXEC.","commonSituations":"Using commands whose response shaping is non-standard inside a cluster pipeline; version mismatch between client and server (new command returning unexpected framing); rare parser bugs.","solutions":["Upgrade redis-py to the latest patch release — protocol-length mismatches are usually fixed promptly.","Isolate which queued command triggers the mismatch by reducing the pipeline to a minimal reproducer and report it with the redis-py and Redis server versions.","As a workaround, run the suspect command outside the pipeline (await client.execute_command(...)) until the bug is fixed."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"from redis.exceptions import InvalidPipelineStack\ntry:\n    await pipe.execute()\nexcept InvalidPipelineStack as e:\n    logger.error('protocol desync: %s', e)\n    # reduce pipeline to minimal repro; upgrade redis-py; file issue","preventionTips":["Keep redis-py up to date.","Report a minimal reproducer with client and server versions."],"tags":["cluster","pipeline","transaction","protocol","assertion"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}