HumanSignal/label-studio · error · ValidationError
Task root must be dict with "data", "meta", "annotations"…
Error message
Task root must be dict with "data", "meta", "annotations", "predictions" fields
What it means
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'.
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
Example fix
// before
import_tasks(['just some text'])
// after
import_tasks([{'data': {'text': 'just some text'}}]) Defensive patterns
Strategy: validation
Validate before calling
def ensure_task_items(items):
for i, item in enumerate(items):
if not isinstance(item, dict):
raise ValueError(f'item {i} must be a dict, got {type(item).__name__}') Type guard
def is_task_item(x):
return isinstance(x, dict) Try / catch
try:
import_tasks(raw_items)
except ValidationError as e:
if 'Task root must be dict' in str(e.detail[0]):
items = [{'data': {'text': it}} if isinstance(it, str) else {'data': {}} for it in raw_items]
import_tasks(items) Prevention
- 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()
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Task[ ] must be
- data[' ']= is of type ' ', but the object tag expects the…
- data is not a list
- Field "data" must be string or dict, but not
- Import data contains completed_by=
AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29).
Data as JSON: /api/errors/d754c46bd33cfd7c.
Report an issue: GitHub.
Appendix: source
Thrown at label_studio/tasks/validation.py:158
# self.instance is loaded by get_object of view
if self.instance and hasattr(self.instance, 'data'):
if isinstance(self.instance.data, dict):
data = self.instance.data
elif isinstance(self.instance.data, str):
try:
data = json.loads(self.instance.data)
except ValueError as e:
raise ValidationError("Can't parse task data: " + extract_message(e))
else:
raise ValidationError(
'Field "data" must be string or dict, but not "' + type(self.instance.data) + '"'
)
self.check_data_and_root(self.instance.project, data)
return task
# check task is dict
if not isinstance(task, dict):
raise ValidationError('Task root must be dict with "data", "meta", "annotations", "predictions" fields')
# task[data] | task[annotations] | task[predictions] | task[meta]
if self.check_allowed(task):
# task[data]
self.raise_if_wrong_class(task, 'data', (dict, list))
self.check_data_and_root(self.project, task['data'])
# task[annotations]: we can't use AnnotationSerializer for validation
# because it's much different with validation we need here
self.raise_if_wrong_class(task, 'annotations', list)
for annotation in task.get('annotations', []):
if not isinstance(annotation, dict):
logger.warning('Annotation must be dict, but "%s" found', str(type(annotation)))
continue
ok = 'result' in annotation
if not ok:
raise ValidationError('Annotation must have "result" fields')View on GitHub (pinned to 0b49e9b539)