redis/redis-py · error · DataError

Category "{category}" must be prefixed with "+" or "-"

Error message

Category "{category}" must be prefixed with "+" or "-"

What it means

Raised by acl_setuser() as a DataError when an entry in categories does not start with '+' or '-' (the '@category' suffix is optional and auto-added by the client). Categories grant/deny command categories like @read, @write, @dangerous. The decoded category value is shown. See redis/commands/core.py:521-537.

Source

Thrown at redis/commands/core.py:534

                    )

        if nopass:
            pieces.append(b"nopass")

        if categories:
            for category in categories:
                category = encoder.encode(category)
                # 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

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Prefix each category with '+' (grant) or '-' (revoke), e.g. ['+read', '-dangerous']. The '@' is optional.
  2. Normalize: categories = [('+' if grant else '-') + c for c in raw].
  3. Drive the sign from a permission model in your config rather than letting callers pass raw strings.

Example fix

# before
r.acl_setuser('alice', enabled=True, categories=['read', 'write'])
# after
r.acl_setuser('alice', enabled=True, categories=['+read', '+write'])
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

def categories_are_prefixed(categories) -> bool:
    return all(c[:1] in ('+', '-') for c in (categories or []))

Try / catch

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

Prevention

When it happens

Trigger: Calling r.acl_setuser(username, categories=['read', 'write']) (no prefix), or a list where any entry lacks +/- . '+@read' and '-@write' are also accepted as-is.

Common situations: Reading category names from config without the permission sign; UI listing categories as bare names; assuming the client defaults to '+' for grant.

Related errors


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