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'.
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)
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[{key}] must be {class_def}
- Import data contains completed_by={completed_by} which is no
- data['{data_key}']={data_value} is of type '{type}', but the
- Field "data" must be string or dict, but not "{type(self.ins
- "result" field in annotation must be list
AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29).
Data as JSON: /api/errors/d754c46bd33cfd7c.
Report an issue: GitHub.