{"id":"b731b7b0ff23e080","repo":"redis/redis-py","slug":"category-category-must-be-prefixed-with-or","errorCode":null,"errorMessage":"Category \"{category}\" must be prefixed with \"+\" or \"-\"","messagePattern":"Category \"(.+?)\" must be prefixed with \"\\+\" or \"-\"","errorType":"validation","errorClass":"DataError","httpStatus":null,"severity":"error","filePath":"redis/commands/core.py","lineNumber":534,"sourceCode":"                    )\n\n        if nopass:\n            pieces.append(b\"nopass\")\n\n        if categories:\n            for category in categories:\n                category = encoder.encode(category)\n                # 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","sourceCodeStart":516,"sourceCodeEnd":552,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/commands/core.py#L516-L552","documentation":"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.","triggerScenarios":"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.","commonSituations":"Reading category names from config without the permission sign; UI listing categories as bare names; assuming the client defaults to '+' for grant.","solutions":["Prefix each category with '+' (grant) or '-' (revoke), e.g. ['+read', '-dangerous']. The '@' is optional.","Normalize: categories = [('+' if grant else '-') + c for c in raw].","Drive the sign from a permission model in your config rather than letting callers pass raw strings."],"exampleFix":"# before\nr.acl_setuser('alice', enabled=True, categories=['read', 'write'])\n# after\nr.acl_setuser('alice', enabled=True, categories=['+read', '+write'])","handlingStrategy":"validation","validationCode":"def _prefixed_categories(items):\n    for c in items:\n        if c[:1] not in ('+', '-'):\n            raise ValueError(f'category {c!r} needs +/- prefix')\n    return items\nclient.acl_setuser(username, categories=_prefixed_categories(categories or []))","typeGuard":"def categories_are_prefixed(categories) -> bool:\n    return all(c[:1] in ('+', '-') for c in (categories or []))","tryCatchPattern":"from redis.exceptions import DataError\ntry:\n    client.acl_setuser(username, categories=categories)\nexcept DataError:\n    categories = ['+' + c.lstrip('+-@') for c in categories]\n    client.acl_setuser(username, categories=categories)","preventionTips":["Always pair a category name with a grant/revoke sign in config.","The '@' is optional; the sign is not."],"tags":["acl","validation","setuser","category","prefix"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}