HumanSignal/label-studio · warning · ValidationError
empty
empty
Error message
empty
What it means
Raised by TaskListSerializer.to_internal_value when the submitted list is empty and allow_empty is False. A POST with an empty array of tasks yields the non-field error 'empty' (code empty); if the serializer is a partial child of a parent serializer, SkipField is raised instead.
Source
Thrown at label_studio/tasks/serializers.py:533
code = f' {detail[0].code}' if detail[0].code != 'invalid' else ''
return f'Error{code} at item {i}: {detail[0]} :: {item}'
else:
errors = ', '.join(detail)
codes = [d.code for d in detail]
return f'Errors {codes} at item {i}: {errors} :: {item}'
def to_internal_value(self, data):
"""Body of run_validation for all data items"""
if data is None:
raise ValidationError('All tasks are empty (None)')
if not isinstance(data, list):
raise ValidationError({api_settings.NON_FIELD_ERRORS_KEY: 'not a list'}, code='not_a_list')
if not self.allow_empty and len(data) == 0:
if self.parent and self.partial:
raise SkipField()
raise ValidationError({api_settings.NON_FIELD_ERRORS_KEY: 'empty'}, code='empty')
ret, errors = [], []
self.annotation_count, self.prediction_count = 0, 0
for i, item in enumerate(data):
try:
validated = self.child.validate(item)
except ValidationError as exc:
error = self.format_error(i, exc.detail, item)
errors.append(error)
# do not print to user too many errors
if len(errors) >= 100:
errors[99] = '...'
break
else:
ret.append(validated)
errors.append({})
if 'annotations' in item:View on GitHub (pinned to 0b49e9b539)
Solutions
- Only call the import API when you have at least one task; skip the request entirely if the list is empty
- Check upstream data extraction (file/CSV/query) for empty results and fail early with a clearer message
- If empty imports should be legal for your workflow, relax the empty-data guard before sending or adjust the serializer usage
- Add client-side logging to distinguish 'no data fetched' from 'import failed'
Example fix
// before
await fetch(`/api/projects/${id}/import`, { method: 'POST', body: JSON.stringify(tasks) });
// after
if (tasks.length === 0) return; // nothing to import
await fetch(`/api/projects/${id}/import`, { method: 'POST', body: JSON.stringify(tasks) }); Defensive patterns
Strategy: validation
Validate before calling
if (Array.isArray(tasks) && tasks.length === 0) {
console.info('no tasks to import, skipping API call');
return;
} Type guard
function isNonEmptyTaskList(v) {
return Array.isArray(v) && v.length > 0;
} Try / catch
try {
await importTasks(projectId, tasks);
} catch (e) {
if (JSON.stringify(e.response?.data || {}).includes('"empty"')) {
console.warn('Empty task list rejected — check upstream data source');
} else throw e;
} Prevention
- Skip the import call when the list is empty instead of sending []
- Alert on upstream sources (files, queries) returning zero rows
- Distinguish intentional no-op syncs from data-loss regressions with logs/metrics
When it happens
Trigger: POST to /api/projects/<id>/import or /api/tasks/ with a zero-length JSON array ([]) while the serializer disallows empty input.
Common situations: Upstream pipeline produced no rows (empty CSV/JSON file); a filter or transformation removed all tasks before submission; re-running an import script after data was already consumed; sync jobs posting deltas that happen to be empty.
Related errors
- All tasks are empty (None)
- not_a_list
- load_tasks: No tasks added
- If you use "predictions" field in the task, you must put "da
- If you use "annotations" field in the task, you must put "da
AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29).
Data as JSON: /api/errors/c6247390ce6859f1.
Report an issue: GitHub.