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

  1. Wrap single region objects in a list: result=[region_dict]
  2. Ensure the value is JSON null-free and is an array of region objects, each with id/from_name/type/value
  3. Check upstream code that may unwrap lists (e.g. result['result'][0]) and resend the full list
  4. 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

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


AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29). Data as JSON: /api/errors/5fc0060c1ad532b2. Report an issue: GitHub.