HumanSignal/label-studio · error · ValidationError
load_tasks: Data root must be list
Error message
load_tasks: Data root must be list
What it means
ValidationError raised by load_tasks_for_async_import in label_studio/data_import/uploader.py. After resolving tasks from uploaded files, URL, or inline project_import.tasks, the data root must be a JSON list. If the parsed payload is a dict or scalar, the async import aborts with 'load_tasks: Data root must be list'.
Source
Thrown at label_studio/data_import/uploader.py:235
else:
could_be_tasks_list = False
(
data_keys,
found_formats,
tasks,
file_upload_ids,
could_be_tasks_list,
) = tasks_from_url(file_upload_ids, project_import.project, user, url, could_be_tasks_list)
if could_be_tasks_list:
project_import.could_be_tasks_list = True
project_import.save(update_fields=['could_be_tasks_list'])
elif project_import.tasks:
tasks = project_import.tasks
# check is data root is list
if not isinstance(tasks, list):
raise ValidationError('load_tasks: Data root must be list')
# empty tasks error
if not tasks:
raise ValidationError('load_tasks: No tasks added')
check_max_task_number(tasks)
return tasks, file_upload_ids, found_formats, list(data_keys)
def load_tasks_for_async_import_streaming(project_import, user, batch_size=1000):
"""Load tasks from different types of request.data / request.files saved in project_import model,
yielding tasks in batches to reduce memory usage"""
from django.conf import settings
if not batch_size:
batch_size = settings.IMPORT_BATCH_SIZE
all_file_upload_ids = []View on GitHub (pinned to 0b49e9b539)
Solutions
- Reformat the JSON so the top level is an array: [{...}, {...}].
- If your data is {"tasks": [...]}, upload only the inner array (save data['tasks'] to a new file).
- Wrap a single record in a list before importing.
- If a converter produced the wrapper, fix the export settings to emit a bare records array.
Example fix
// before (rejected)
{"tasks": [{"data": {"text": "hi"}}]}
// after (accepted)
[{"data": {"text": "hi"}}] Defensive patterns
Strategy: type-guard
Validate before calling
import json
payload = json.load(open(path))
if not isinstance(payload, list):
# unwrap common wrappers
payload = payload.get('tasks', [payload] if isinstance(payload, dict) else None)
json.dump(payload, open('import.json', 'w')) Type guard
def is_task_list(payload):
return isinstance(payload, list) and all(isinstance(t, dict) for t in payload) Try / catch
try:
import_tasks(payload)
except ValidationError as e:
if 'Data root must be list' in str(e):
payload = [payload] if isinstance(payload, dict) else payload['tasks']
import_tasks(payload)
else:
raise Prevention
- Always export/import JSON arrays: [{"data": {...}}, ...].
- Unwrap keys like {"tasks": [...]} or {"items": [...]} before import.
- Validate with json.load + isinstance(payload, list) before calling the API.
- Test one small file through the import endpoint before large uploads.
When it happens
Trigger: async_import_background -> load_tasks_for_async_import where the imported JSON file/payload parses to an object (e.g. {"tasks": [...]} or a single dict) instead of a top-level array.
Common situations: Uploading a JSON file shaped as {"data": {...}} or a config-style object; exporting a single task as an object; user pastes JSON with a wrapper key; migrating from another tool that emits object-wrapped arrays.
Related errors
- load_tasks: No tasks added
- Maximum task number is {settings.TASKS_MAX_NUMBER}, current
- Maximum total size of all files is {settings.TASKS_MAX_FILE_
- {ext} extension is not supported
- extract_message(e)
AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29).
Data as JSON: /api/errors/7e894643a0955b3d.
Report an issue: GitHub.