redis/redis-py · error · RedisClusterException

method discard() is not supported outside of transactional c

Error message

method discard() is not supported outside of transactional context

What it means

Raised unconditionally by PipelineStrategy.discard() — the non-transactional cluster pipeline strategy. DISCARD aborts a MULTI transaction; a non-transactional cluster pipeline has no transaction to discard, so the call is rejected. To clear a non-transactional pipeline's queued commands, use reset() instead.

Source

Thrown at redis/asyncio/cluster.py:3047

        self._command_queue = []

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

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

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

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

    async def unlink(self, *names):
        if len(names) != 1:
            raise RedisClusterException(
                "unlinking multiple keys is not implemented in pipeline command"
            )

        return self.execute_command("UNLINK", names[0])


class TransactionStrategy(AbstractStrategy):
    NO_SLOTS_COMMANDS = {"UNWATCH"}
    IMMEDIATE_EXECUTE_COMMANDS = {"WATCH", "UNWATCH"}
    UNWATCH_COMMANDS = {"DISCARD", "EXEC", "UNWATCH"}
    SLOT_REDIRECT_ERRORS = (AskError, MovedError)
    CONNECTION_ERRORS = (

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. For a non-transactional pipeline, call await pipe.reset() to clear queued commands instead of discard().
  2. If you need true transactional DISCARD semantics, use pipe = rc.pipeline(transaction=True).
  3. Restructure error handlers so discard() only runs when transaction=True was used.

Example fix

// before
pipe = rc.pipeline()
await pipe.discard()  # raises

// after
pipe = rc.pipeline()
await pipe.reset()
Defensive patterns

Strategy: validation

Validate before calling

async def clear_pipeline(pipe):
    if getattr(pipe, '_transaction', False):
        await pipe.discard()
    else:
        await pipe.reset()

Type guard

from redis.asyncio.cluster import TransactionStrategy

def supports_discard(pipe) -> bool:
    return isinstance(getattr(pipe, '_strategy', None), TransactionStrategy)

Try / catch

from redis.cluster import RedisClusterException

try:
    await pipe.discard()
except RedisClusterException as e:
    if 'discard()' in str(e):
        await pipe.reset()  # non-transactional equivalent
    else:
        raise

Prevention

When it happens

Trigger: Calling await pipe.discard() on a pipeline created via rc.pipeline() or rc.pipeline(transaction=False). Transactional pipelines (transaction=True) support discard.

Common situations: Porting standalone pipeline abort logic to cluster mode; calling discard() in error-handling code on the wrong pipeline flavor; expecting DISCARD to clear a non-transactional command queue.

Related errors


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