redis/redis-py · error · RedisClusterException

method multi() is not supported outside of transactional con

Error message

method multi() is not supported outside of transactional context

What it means

PipelineStrategy.multi() (redis/cluster.py:4524) unconditionally raises because MULTI only makes sense inside a transactional pipeline. The execution strategy is chosen in ClusterPipeline.__init__ (redis/cluster.py:3598): PipelineStrategy is used when transaction=False, TransactionStrategy when transaction=True. Calling multi() on a non-transactional pipeline is a contradiction.

Source

Thrown at redis/cluster.py:4525

        policy_callback = self._pipe._policies_callback_mapping[request_policy]

        if request_policy == RequestPolicy.DEFAULT_KEYED:
            nodes = policy_callback(command, *args)
        elif request_policy == RequestPolicy.MULTI_SHARD:
            nodes = policy_callback(*args, **kwargs)
        elif request_policy == RequestPolicy.DEFAULT_KEYLESS:
            nodes = policy_callback(args[0])
        else:
            nodes = policy_callback()

        if args[0].lower() == "ft.aggregate":
            self._aggregate_nodes = nodes

        return nodes

    def multi(self):
        raise RedisClusterException(
            "method multi() is not supported outside of transactional context"
        )

    def discard(self):
        raise RedisClusterException(
            "method discard() is not supported outside of transactional context"
        )

    def watch(self, *names):
        raise RedisClusterException(
            "method watch() is not supported outside of transactional context"
        )

    def unwatch(self, *names):
        raise RedisClusterException(
            "method unwatch() is not supported outside of transactional context"
        )

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Use a transactional pipeline: rc.pipeline(transaction=True) (the default) before calling pipe.multi().
  2. If you intentionally use transaction=False, remove all multi()/discard()/watch()/unwatch() calls from that code path.
  3. Branch on the pipeline's mode if shared code is unavoidable: only call multi() when transaction is enabled.

Example fix

# before
pipe = rc.pipeline(transaction=False)
pipe.multi()  # raises

# after
pipe = rc.pipeline(transaction=True)
pipe.multi()
pipe.set('k', 'v')
pipe.execute()
Defensive patterns

Strategy: validation

Validate before calling

def open_transactional_pipeline(client, transaction=True):
    pipe = client.pipeline(transaction=transaction)
    if not transaction:
        # multi() is illegal here; make that explicit at construction time
        object.__setattr__(pipe, '_no_multi', True)
    return pipe

def safe_multi(pipe):
    if getattr(pipe, '_no_multi', False):
        raise RuntimeError('multi() not allowed on a non-transactional pipeline')
    pipe.multi()

Try / catch

from redis.exceptions import RedisClusterException

try:
    pipe.multi()
except RedisClusterException:
    pipe = rc.pipeline(transaction=True)
    pipe.multi()

Prevention

When it happens

Trigger: Creating rc.pipeline(transaction=False) (or rc.pipeline() with a False transaction argument) and then calling pipe.multi(). The non-transactional strategy rejects it.

Common situations: Switching a pipeline to transaction=False to avoid EXEC overhead, then leaving MULTI/WATCH calls in place. Code paths shared between transactional and non-transactional pipelines. Misreading the standalone pipeline API where multi() can sometimes be called late.

Related errors


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/402d3db6aefbffb7.json. Report an issue: GitHub.