{"record":{"id":"1777297d646b788b","repo":"redis/redis-py","slug":"cannot-issue-nested-calls-to-multi","errorCode":null,"errorMessage":"Cannot issue nested calls to MULTI","messagePattern":"Cannot issue nested calls to MULTI","errorType":"exception","errorClass":"RedisError","httpStatus":null,"severity":"error","filePath":"redis/asyncio/client.py","lineNumber":1895,"sourceCode":"            # the pipeline must not be left holding a reference to a\n            # connection that is being returned to the pool. Shield the\n            # release itself so a second cancel cannot split the pool's\n            # internal in-use/available bookkeeping mid-update.\n            if self.connection:\n                connection, self.connection = self.connection, None\n                await asyncio.shield(self.connection_pool.release(connection))\n\n    async def aclose(self) -> None:\n        \"\"\"Alias for reset(), a standard method name for cleanup\"\"\"\n        await self.reset()\n\n    def multi(self):\n        \"\"\"\n        Start a transactional block of the pipeline after WATCH commands\n        are issued. End the transactional block with `execute`.\n        \"\"\"\n        if self.explicit_transaction:\n            raise RedisError(\"Cannot issue nested calls to MULTI\")\n        if self.command_stack:\n            raise RedisError(\n                \"Commands without an initial WATCH have already been issued\"\n            )\n        self.explicit_transaction = True\n\n    def execute_command(\n        self, *args, **kwargs\n    ) -> Union[\"Pipeline\", Awaitable[\"Pipeline\"]]:\n        if (self.watching or args[0] == \"WATCH\") and not self.explicit_transaction:\n            return self.immediate_execute_command(*args, **kwargs)\n        return self.pipeline_execute_command(*args, **kwargs)\n\n    async def _disconnect_reset_raise_on_watching(\n        self,\n        conn: Connection,\n        error: Exception,\n        failure_count: Optional[int] = None,","sourceCodeStart":1877,"sourceCodeEnd":1913,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/asyncio/client.py#L1877-L1913","documentation":"Raised by Pipeline.multi() when self.explicit_transaction is already True. MULTI is a Redis command that begins a transaction; calling .multi() twice mirrors sending MULTI twice, which the server rejects. The library enforces it client-side before the second MULTI is queued.","triggerScenarios":"Calling pipe.multi() twice on the same async pipeline: pipe = client.multi_transaction(); ...; pipe.multi(); or calling client.multi() inside a context manager that itself issues MULTI.","commonSituations":"Wrapping pipeline usage in a helper that calls multi() and the caller also calls it; porting sync code that relied on the transactional=True default without realizing multi() is explicit.","solutions":["Call pipe.multi() exactly once per transaction; subsequent commands are queued automatically.","Use the async context manager form (`async with client.pipeline(transaction=True) as pipe:`) which manages MULTI for you.","Remove the redundant explicit multi() call in the caller."],"exampleFix":"// before\npipe = client.multi_transaction()\nawait pipe.set('k', 'v')\npipe.multi()\n// after\npipe = client.multi_transaction()\nawait pipe.set('k', 'v')\n# multi() already called by multi_transaction(); just execute\nawait pipe.execute()","handlingStrategy":"validation","validationCode":"assert not pipe.explicit_transaction, 'multi() already called on this pipeline'\npipe.multi()","typeGuard":"def can_call_multi(pipe) -> bool:\n    return not pipe.explicit_transaction","tryCatchPattern":"try:\n    pipe.multi()\nexcept RedisError as e:\n    if 'nested calls to MULTI' in str(e):\n        pass  # already in transaction\n    else:\n        raise","preventionTips":["Prefer the async context-manager pipeline which manages MULTI for you.","Call multi() at most once per transaction."],"tags":["pipeline","transaction","multi","lifecycle"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}