HumanSignal/label-studio · error · ValidationError
annotation "result" field in annotation must be list
Error message
annotation "result" field in annotation must be list
What it means
AnnotationSerializer.validate_result() requires the (possibly string-parsed) annotation 'result' to be a list of region objects. If the parsed value is a dict, string, number, or null instead of a list, DRF ValidationError is raised with this message. Valid results are lists that are then deduped by (id, from_name, type).
Source
Thrown at label_studio/tasks/serializers.py:202
'UNIQUE constraint failed: task_completion.unique_id',
'duplicate key value violates unique constraint "task_completion_unique_id_key"',
]
if any([error in str(e) for error in errors]):
raise AnnotationDuplicateError()
raise
def validate_result(self, value):
data = value
# convert from str to json if need
if isinstance(value, str):
try:
data = json.loads(value)
except: # noqa: E722
raise ValueError('annotation "result" can\'t be parse from str to JSON')
# check result is list
if not isinstance(data, list):
raise ValidationError('annotation "result" field in annotation must be list')
# FIT-1669: collapse `(id, from_name, type)` collisions at the write boundary
# so the annotation record never persists duplicate-id rows.
return dedupe_annotation_result_list(data)
def _resolve_project_for_validation(self, data):
if 'task' in data:
return data['task'].project
if self.instance is not None:
return self.instance.project
task = self.context.get('task')
if task is not None:
return task.project
return None
def validate(self, data):
"""Validate annotation result against project config and custom interface output_schema."""
if 'result' not in data or data.get('was_cancelled') is True:View on GitHub (pinned to 0b49e9b539)
Solutions
- Wrap single region objects in a list: result=[region_dict]
- Ensure the value is JSON null-free and is an array of region objects, each with id/from_name/type/value
- Check upstream code that may unwrap lists (e.g. result['result'][0]) and resend the full list
- Validate client-side: isinstance(result, list) and all(isinstance(r, dict) for r in result)
Example fix
// before
{"result": {"id": 1, "from_name": "label", "type": "labels", "value": {"labels": ["cat"]}}}
// after
{"result": [{"id": 1, "from_name": "label", "type": "labels", "value": {"labels": ["cat"]}}]} Defensive patterns
Strategy: type-guard
Validate before calling
def assert_result_is_region_list(result):
data = json.loads(result) if isinstance(result, str) else result
if not isinstance(data, list) or not all(isinstance(r, dict) for r in data):
raise ValueError('annotation result must be a list of region dicts') Type guard
def is_region_list(value) -> bool:
import json
data = json.loads(value) if isinstance(value, str) else value
return isinstance(data, list) and all(isinstance(r, dict) for r in data) Try / catch
from rest_framework.exceptions import ValidationError
try:
ser = AnnotationSerializer(data=payload)
ser.is_valid(raise_exception=True)
except ValidationError as e:
logger.error("annotation result must be a list: %s", e.detail) Prevention
- Always wrap even a single region in a list before POSTing annotations
- Don't confuse prediction 'value' dicts with the annotation result list format
- Assert result is a non-null list in client code before serialization
- Round-trip test your annotation payloads through json.loads to confirm shapes
When it happens
Trigger: Posting an annotation where result is {"key": ...} (a single region object not wrapped in a list), a bare string like "ok", or null — via the annotations API or AnnotationSerializer.
Common situations: Clients sending one region object directly instead of [region]; APIs that return a dict being echoed back as a result; result:null from failed pipelines; confusing the prediction 'value' dict format with the annotation result list format.
Related errors
- Each item in prediction result should be dict
- annotation "result" can't be parse from str to JSON
- {connection validation error}
- exc.kwargs['report']
- Wrong credentials for S3 {bucket_name}
AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29).
Data as JSON: /api/errors/5fc0060c1ad532b2.
Report an issue: GitHub.