{"record":{"id":"911f4f1439b41140","repo":"redis/redis-py","slug":"command-encoder-decode-cmd-force-true-must-b","errorCode":null,"errorMessage":"Command \"{encoder.decode(cmd, force=True)}\" must be prefixed with \"+\" or \"-\"","messagePattern":"Command \"(.+?)\" must be prefixed with \"\\+\" or \"-\"","errorType":"exception","errorClass":"DataError","httpStatus":null,"severity":"error","filePath":"redis/commands/core.py","lineNumber":542,"sourceCode":"                # categories can be prefixed with one of (+@, +, -@, -)\n                if category.startswith(b\"+@\"):\n                    pieces.append(category)\n                elif category.startswith(b\"+\"):\n                    pieces.append(b\"+@%s\" % category[1:])\n                elif category.startswith(b\"-@\"):\n                    pieces.append(category)\n                elif category.startswith(b\"-\"):\n                    pieces.append(b\"-@%s\" % category[1:])\n                else:\n                    raise DataError(\n                        f'Category \"{encoder.decode(category, force=True)}\" '\n                        'must be prefixed with \"+\" or \"-\"'\n                    )\n        if commands:\n            for cmd in commands:\n                cmd = encoder.encode(cmd)\n                if not cmd.startswith(b\"+\") and not cmd.startswith(b\"-\"):\n                    raise DataError(\n                        f'Command \"{encoder.decode(cmd, force=True)}\" '\n                        'must be prefixed with \"+\" or \"-\"'\n                    )\n                pieces.append(cmd)\n\n        if keys:\n            for key in keys:\n                key = encoder.encode(key)\n                if not key.startswith(b\"%\") and not key.startswith(b\"~\"):\n                    key = b\"~%s\" % key\n                pieces.append(key)\n\n        if channels:\n            for channel in channels:\n                channel = encoder.encode(channel)\n                pieces.append(b\"&%s\" % channel)\n\n        if selectors:","sourceCodeStart":524,"sourceCodeEnd":560,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/commands/core.py#L524-L560","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Prefix every commands entry with '+' (grant) or '-' (revoke), e.g. '+get', '-flushdb'.","For grouping, use `categories` with '+@name' instead of listing many commands.","Validate prefixes upstream in your permissions builder."],"exampleFix":"# before\nclient.acl_setuser('alice', commands=['get', 'set'])\n# after\nclient.acl_setuser('alice', commands=['+get', '+set'])","handlingStrategy":"validation","validationCode":"def normalize_commands(commands):\n    out = []\n    for c in commands:\n        if not (c.startswith('+') or c.startswith('-')):\n            c = '+' + c  # default to grant\n        out.append(c)\n    return out\n\ndef safe_acl_setuser_commands(client, username, commands):\n    return client.acl_setuser(username, commands=normalize_commands(commands))","typeGuard":"def is_prefixed_command(c) -> bool:\n    return isinstance(c, str) and len(c) > 1 and c[0] in '+-'","tryCatchPattern":"from redis.exceptions import DataError\ntry:\n    client.acl_setuser('alice', commands=commands)\nexcept DataError as e:\n    if 'must be prefixed' in str(e):\n        commands = ['+' + c if not c[:1] in '+-' else c for c in commands]\n        client.acl_setuser('alice', commands=commands)\n    else:\n        raise","preventionTips":["Always prefix command permissions with '+'/'-'.","For grouped permissions, prefer `categories` ('+@name') over long command lists.","Validate ACL inputs in a shared helper reused across callsites."],"tags":["acl","validation","acl-setuser","commands","input-validation","prefix"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}