{"record":{"id":"eb208297570b41d4","repo":"HumanSignal/label-studio","slug":"task-key-must-be-class-def","errorCode":null,"errorMessage":"Task[{key}] must be {class_def}","messagePattern":"Task\\[(.+?)\\] must be (.+?)","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"label_studio/tasks/validation.py","lineNumber":131,"sourceCode":"                raise ValidationError(e.detail[0] + ' [assume: item[\"data\"] = task root with values]')\n\n    @staticmethod\n    def check_allowed(task):\n        # task is required\n        if 'data' not in task:\n            return False\n\n        # everything is ok\n        return True\n\n    @staticmethod\n    def raise_if_wrong_class(task, key, class_def):\n        if key in task and not isinstance(task[key], class_def):\n            if isinstance(class_def, tuple):\n                class_def = ' or '.join([c.__name__ for c in class_def])\n            else:\n                class_def = class_def.__name__\n            raise ValidationError('Task[{key}] must be {class_def}'.format(key=key, class_def=class_def))\n\n    def validate(self, task):\n        \"\"\"Validate whole task with task['data'] and task['annotations']. task['predictions']\"\"\"\n        # task is class\n        if hasattr(task, 'data'):\n            self.check_data_and_root(self.project, task.data)\n            return task\n\n        # self.instance is loaded by get_object of view\n        if self.instance and hasattr(self.instance, 'data'):\n            if isinstance(self.instance.data, dict):\n                data = self.instance.data\n            elif isinstance(self.instance.data, str):\n                try:\n                    data = json.loads(self.instance.data)\n                except ValueError as e:\n                    raise ValidationError(\"Can't parse task data: \" + extract_message(e))\n            else:","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/tasks/validation.py#L113-L149","documentation":"TaskValidator.raise_if_wrong_class enforces that optional task-root fields have specific Python types: 'data' must be dict or list, 'annotations' a list, 'predictions' a list, 'meta' a dict or list. If the key is present with the wrong type, this ValidationError names the required class (joined with ' or ' for tuples).","triggerScenarios":"validate() calling raise_if_wrong_class with e.g. {'data': 'not-a-dict'}, {'annotations': {'result': []}} (dict instead of list), or {'predictions': 'pending'}.","commonSituations":"Putting pre-annotations under 'annotations' as a dict instead of a list of annotation objects; string-encoded JSON left in 'data' instead of a parsed dict; sending 'predictions' as a single object rather than an array.","solutions":["Ensure task['data'] is a dict (or list), task['annotations'] and task['predictions'] are lists, task['meta'] is a dict or list","Parse stringified JSON before submission (json.loads the data string) so 'data' is a real dict","Move a single annotation/prediction into a one-element list: [prediction]","Pre-validate the task root shape before calling the API"],"exampleFix":"// before\n{'data': '{\"text\": \"hi\"}', 'predictions': {'result': []}}\n// after\n{'data': {'text': 'hi'}, 'predictions': [{'result': []}]}","handlingStrategy":"type-guard","validationCode":"def check_root_shape(task):\n    assert 'data' not in task or isinstance(task['data'], (dict, list))\n    assert 'annotations' not in task or isinstance(task['annotations'], list)\n    assert 'predictions' not in task or isinstance(task['predictions'], list)\n    assert 'meta' not in task or isinstance(task['meta'], (dict, list))","typeGuard":"def task_root_ok(task):\n    return (isinstance(task, dict)\n            and (not isinstance(task.get('data'), str))\n            and isinstance(task.get('annotations', []), list)\n            and isinstance(task.get('predictions', []), list))","tryCatchPattern":"try:\n    import_tasks(tasks)\nexcept ValidationError as e:\n    m = re.search(r'Task\\[(\\w+)\\] must be ([\\w or ]+)', str(e.detail[0]))\n    if m:\n        coerce_task_field(tasks, m.group(1), m.group(2))","preventionTips":["json.loads any stringified data before submission","Always send annotations/predictions as arrays, even for single items","Keep 'meta' as a dict; don't overload 'predictions' for status strings"],"tags":["validation","type-mismatch","task-structure"],"backgroundTag":"schema-validation-failed","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}