{"record":{"id":"d754c46bd33cfd7c","repo":"HumanSignal/label-studio","slug":"task-root-must-be-dict-with-data-meta-annot","errorCode":null,"errorMessage":"Task root must be dict with \"data\", \"meta\", \"annotations\", \"predictions\" fields","messagePattern":"Task root must be dict with \"data\", \"meta\", \"annotations\", \"predictions\" fields","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"label_studio/tasks/validation.py","lineNumber":158,"sourceCode":"        # 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:\n                raise ValidationError(\n                    'Field \"data\" must be string or dict, but not \"' + type(self.instance.data) + '\"'\n                )\n            self.check_data_and_root(self.instance.project, data)\n            return task\n\n        # check task is dict\n        if not isinstance(task, dict):\n            raise ValidationError('Task root must be dict with \"data\", \"meta\", \"annotations\", \"predictions\" fields')\n\n        # task[data] | task[annotations] | task[predictions] | task[meta]\n        if self.check_allowed(task):\n            # task[data]\n            self.raise_if_wrong_class(task, 'data', (dict, list))\n            self.check_data_and_root(self.project, task['data'])\n\n            # task[annotations]: we can't use AnnotationSerializer for validation\n            # because it's much different with validation we need here\n            self.raise_if_wrong_class(task, 'annotations', list)\n            for annotation in task.get('annotations', []):\n                if not isinstance(annotation, dict):\n                    logger.warning('Annotation must be dict, but \"%s\" found', str(type(annotation)))\n                    continue\n\n                ok = 'result' in annotation\n                if not ok:\n                    raise ValidationError('Annotation must have \"result\" fields')","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/tasks/validation.py#L140-L176","documentation":"When validate() receives neither an object with .data nor a populated self.instance, it falls back to treating the argument as a task-root dict. If the argument is not a dict at all (string, list, int, None-with-.data absent, etc.), this ValidationError explains the expected root shape: a dict optionally containing 'data', 'meta', 'annotations', 'predictions'.","triggerScenarios":"Calling TaskSerializer/TaskValidator.validate with a bare string ('some text'), a list of values, or any non-dict item inside the import batch; to_internal_value iterates data items and each non-dict item reaches this branch unless check_allowed wraps it first (only dicts reach check_allowed's else path).","commonSituations":"Import payloads where individual items are plain strings/numbers instead of objects; users passing the whole batch to validate instead of one item; CSV importers emitting rows as lists rather than dicts; SDK misuse passing raw text to import_tasks.","solutions":["Wrap each item as a dict: {'data': {...}} at minimum","If passing raw data (e.g. {'text': 'x'}), that dict takes the root-assumption path; but non-dicts like 'just text' must be {'data': {'text': 'just text'}}","Ensure the batch is a list of dicts, not a list of strings/lists","Coerce CSV rows with dict(zip(headers, row)) before importing"],"exampleFix":"// before\nimport_tasks(['just some text'])\n// after\nimport_tasks([{'data': {'text': 'just some text'}}])","handlingStrategy":"validation","validationCode":"def ensure_task_items(items):\n    for i, item in enumerate(items):\n        if not isinstance(item, dict):\n            raise ValueError(f'item {i} must be a dict, got {type(item).__name__}')","typeGuard":"def is_task_item(x):\n    return isinstance(x, dict)","tryCatchPattern":"try:\n    import_tasks(raw_items)\nexcept ValidationError as e:\n    if 'Task root must be dict' in str(e.detail[0]):\n        items = [{'data': {'text': it}} if isinstance(it, str) else {'data': {}} for it in raw_items]\n        import_tasks(items)","preventionTips":["Import batches must be lists of dicts, never lists of strings/lists","Convert CSV/TSV rows with dict(zip(headers, row)) before import","One item per validate() call — don't pass a whole list to validate()"],"tags":["validation","task-structure","type-mismatch"],"backgroundTag":"schema-validation-failed","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}