redis/redis-py · error · DataError

Command "{encoder.decode(cmd, force=True)}" must be prefixed

Error message

Command "{encoder.decode(cmd, force=True)}" must be prefixed with "+" or "-"

What it means

Raised by Redis.acl_setuser() when an entry in the `commands` list does not begin with '+' or '-'. Each command permission must be prefixed to indicate grant or revoke (e.g. '+get', '-flushdb'). The library inspects the first byte of the encoded command and rejects entries missing the prefix; the decoded command name is shown in the message.

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 6a6b581b48)

Solutions

  1. Prefix every commands entry with '+' (grant) or '-' (revoke), e.g. '+get', '-flushdb'.
  2. For grouping, use `categories` with '+@name' instead of listing many commands.
  3. Validate prefixes upstream in your permissions builder.

Example fix

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

Strategy: validation

Validate before calling

def normalize_commands(commands):
    out = []
    for c in commands:
        if not (c.startswith('+') or c.startswith('-')):
            c = '+' + c  # default to grant
        out.append(c)
    return out

def safe_acl_setuser_commands(client, username, commands):
    return client.acl_setuser(username, commands=normalize_commands(commands))

Type guard

def is_prefixed_command(c) -> bool:
    return isinstance(c, str) and len(c) > 1 and c[0] in '+-'

Try / catch

from redis.exceptions import DataError
try:
    client.acl_setuser('alice', commands=commands)
except DataError as e:
    if 'must be prefixed' in str(e):
        commands = ['+' + c if not c[:1] in '+-' else c for c in commands]
        client.acl_setuser('alice', commands=commands)
    else:
        raise

Prevention

When it happens

Trigger: Calling client.acl_setuser('alice', commands=['get']) with no prefix, commands=['get', '+set'] (mixed), or commands=['@get']. The decoded offending command appears in the message.

Common situations: Passing raw command names from a permissions UI without prefix; confusing command permissions with category permissions (which use '@'); copy-pasting a command list from docs that omit prefixes for brevity.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/911f4f1439b41140. Report an issue: GitHub.