{"record":{"id":"5717445244af48ac","repo":"HumanSignal/label-studio","slug":"list-valued-user-filters-support-only-these-operat","errorCode":null,"errorMessage":"List-valued user filters support only these operators: {allowed}.","messagePattern":"List-valued user filters support only these operators: (.+?)\\.","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"label_studio/data_manager/managers.py","lineNumber":106,"sourceCode":"class ResolvedUserFilterIds(list):\n    \"\"\"Marker for IDs expanded by trusted backend code after client-input validation.\"\"\"\n\n\ndef validate_user_filter_operator(field_name, operator, value):\n    \"\"\"Reject unsupported operators for user-list filters (FIT-2435).\n\n    List-valued membership filters only support contains / not_contains.\n    Empty is allowed only for fields in USER_FILTER_EMPTY_FIELDS.\n    Legacy scalar equal/not_equal/in_list remain accepted so normalize_persisted_user_filter\n    can recover historical views.\n    \"\"\"\n    if field_name not in USER_FILTER_FIELDS:\n        return\n\n    if isinstance(value, list):\n        if operator not in USER_FILTER_VALUE_OPERATORS:\n            allowed = ', '.join(sorted(USER_FILTER_VALUE_OPERATORS))\n            raise ValidationError(f'List-valued user filters support only these operators: {allowed}.')\n        return\n\n    allowed = allowed_user_filter_operators(field_name)\n    if operator in allowed:\n        return\n    if operator in LEGACY_USER_FILTER_OPERATORS:\n        return\n\n    allowed_label = ', '.join(sorted(allowed))\n    raise ValidationError(\n        f'User filter \"{field_name}\" does not support operator \"{operator}\". Allowed: {allowed_label}.'\n    )\n\n\ndef normalize_persisted_user_filter(field_name, operator, value):\n    \"\"\"Recover historical user-filter shapes without relaxing validation for new writes.\"\"\"\n    if field_name not in USER_FILTER_FIELDS:\n        return operator, value","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/data_manager/managers.py#L88-L124","documentation":"validate_user_filter_operator guards filters on built-in user fields (USER_FILTER_FIELDS). A list-valued filter value may only be used with operators in USER_FILTER_VALUE_OPERATORS (list-style operators such as contains/not_contains); anything else raises Django ValidationError listing the allowed operators.","triggerScenarios":"Applying a user filter whose value is a list (e.g. value: [\"a\",\"b\"]) with a scalar operator like equal, not_equal, or empty via apply_filters or the DataManager validate() path.","commonSituations":"A saved/dashboard filter built by an older frontend being replayed with list values; hand-written API filters copying list values onto = operators; tests exercising invalid operator/value combos.","solutions":["Use a list operator (e.g. contains / not_contains) whenever the filter value is a list","Convert the value to a scalar if an equality operator is intended","Normalize legacy shapes with normalize_persisted_user_filter before validating","Check USER_FILTER_VALUE_OPERATORS in managers.py for the exact allowed set"],"exampleFix":"// before\n{\"filter\": \"filter:tasks:created_by\", \"operator\": \"equal\", \"value\": [\"u1\",\"u2\"]}\n// after\n{\"filter\": \"filter:tasks:created_by\", \"operator\": \"contains\", \"value\": [\"u1\",\"u2\"]}","handlingStrategy":"validation","validationCode":"from data_manager.managers import USER_FILTER_FIELDS, USER_FILTER_VALUE_OPERATORS\nif f['filter'] in USER_FILTER_FIELDS and isinstance(f['value'], list) and f['operator'] not in USER_FILTER_VALUE_OPERATORS:\n    raise ValueError(f\"list value needs one of {sorted(USER_FILTER_VALUE_OPERATORS)}\")","typeGuard":"def is_valid_list_user_filter(f): return isinstance(f.get('value'), list) and f.get('operator') in USER_FILTER_VALUE_OPERATORS","tryCatchPattern":"try:\n    apply_filters(...)\nexcept ValidationError as e:\n    if 'List-valued user filters' in str(e): coerce_value_to_scalar_or_list_operator(e)\n    else: raise","preventionTips":["Only offer list operators in the UI when the value widget produces arrays","Normalize legacy filter payloads through normalize_persisted_user_filter","Keep operator menus driven by backend allowlists, not hardcoded lists","Write a schema test asserting operator/value type compatibility"],"tags":["validation","filters","operators","django"],"backgroundTag":"filter-operator-not-allowed","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}