redis/redis-py · error · DataError

Command "{cmd}" must be prefixed with "+" or "-"

Error message

Command "{cmd}" must be prefixed with "+" or "-"

What it means

Raised by acl_setuser() as a DataError when an entry in the commands iterable does not start with '+' or '-'. Each command permission must explicitly grant (+) or revoke (-) a command, optionally with args (e.g. '+get', '-flushdb'). The decoded command is shown. See redis/commands/core.py:538-546.

Source

Thrown at redis/commands/core.py:542

                # categories can be prefixed with one of (+@, +, -@, -)
                if category.startswith(b"+@"):
                    pieces.append(category)
                elif category.startswith(b"+"):
                    pieces.append(b"+@%s" % category[1:])
                elif category.startswith(b"-@"):
                    pieces.append(category)
                elif category.startswith(b"-"):
                    pieces.append(b"-@%s" % category[1:])
                else:
                    raise DataError(
                        f'Category "{encoder.decode(category, force=True)}" '
                        'must be prefixed with "+" or "-"'
                    )
        if commands:
            for cmd in commands:
                cmd = encoder.encode(cmd)
                if not cmd.startswith(b"+") and not cmd.startswith(b"-"):
                    raise DataError(
                        f'Command "{encoder.decode(cmd, force=True)}" '
                        'must be prefixed with "+" or "-"'
                    )
                pieces.append(cmd)

        if keys:
            for key in keys:
                key = encoder.encode(key)
                if not key.startswith(b"%") and not key.startswith(b"~"):
                    key = b"~%s" % key
                pieces.append(key)

        if channels:
            for channel in channels:
                channel = encoder.encode(channel)
                pieces.append(b"&%s" % channel)

        if selectors:

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Prefix each command with '+' (allow) or '-' (deny), e.g. ['+get', '+set', '-flushdb'].
  2. Normalize from a (cmd, allow) tuple: commands = [('+' if a else '-') + c for c, a in rules].
  3. Keep the sign paired with the command in your permission store, not as a separate default.

Example fix

# before
r.acl_setuser('alice', enabled=True, commands=['get', 'set'])
# after
r.acl_setuser('alice', enabled=True, commands=['+get', '+set'])
Defensive patterns

Strategy: validation

Validate before calling

def _prefixed_commands(items):
    for cmd in items:
        if cmd[:1] not in ('+', '-'):
            raise ValueError(f'command {cmd!r} needs +/- prefix')
    return items
client.acl_setuser(username, commands=_prefixed_commands(commands or []))

Type guard

def commands_are_prefixed(commands) -> bool:
    return all(cmd[:1] in ('+', '-') for cmd in (commands or []))

Try / catch

from redis.exceptions import DataError
try:
    client.acl_setuser(username, commands=commands)
except DataError:
    commands = ['+' + c for c in commands]
    client.acl_setuser(username, commands=commands)

Prevention

When it happens

Trigger: Calling r.acl_setuser(username, commands=['get', 'set']) (no prefix), or any command string missing the leading sign.

Common situations: Listing allowed commands as bare names in config; building a permission editor that treats commands as toggleable names without storing the grant/revoke sign.

Related errors


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