HumanSignal/label-studio · error · ValidationError

annotation_ser.errors

Error message

annotation_ser.errors

What it means

This is not a distinct exception but a log/ValidationError payload produced in add_task when annotation serializers fail is_valid() while importing tasks. Label Studio logs the per-annotation DRF serializer errors and, if raise_exception=True, raises ValidationError with the error dict. Invalid annotation rows are silently skipped so the task may persist with fewer annotations than submitted.

Source

Thrown at label_studio/io_storages/base_models.py:626

                # ignores export identity fields; reusing exported unique_id violates the
                # DB unique constraint. Map export id -> import_id for parity with bulk import.
                annotation.pop('unique_id', None)
                export_id = annotation.pop('id', None)
                if export_id is not None and annotation.get('import_id') is None:
                    annotation['import_id'] = export_id
                annotation['task'] = task.id
                annotation['project'] = project.id
            annotation_ser = AnnotationSerializer(data=annotations, many=True)

            # Always validate annotations, but control error handling based on FF
            created_annotations = []
            if annotation_ser.is_valid():
                created_annotations = annotation_ser.save()
            else:
                # Log validation errors but don't save invalid annotations
                logger.error(f'Invalid annotations for task {task.id}: {annotation_ser.errors}')
                if raise_exception:
                    raise ValidationError(annotation_ser.errors)

            # Reconcile the denormalized task counters with what actually persisted. The task
            # above is seeded with counts taken from the *payload* annotations/predictions, but
            # rows can be silently skipped when invalid (under
            # ff_fix_back_dev_3342_storage_scan_with_invalid_annotations, is_valid() fails and we
            # don't raise). Without this, a task whose only annotation was skipped keeps
            # total_annotations=1 while having zero annotation rows — a stale counter the Data
            # Manager reads directly (per-task column and tab totals). Recompute from the rows we
            # actually created so the cached counters can't drift above reality.
            actual_total_annotations = sum(1 for a in created_annotations if not a.was_cancelled)
            actual_cancelled_annotations = sum(1 for a in created_annotations if a.was_cancelled)
            actual_total_predictions = len(created_predictions)
            if (
                task.total_annotations != actual_total_annotations
                or task.cancelled_annotations != actual_cancelled_annotations
                or task.total_predictions != actual_total_predictions
            ):
                task.total_annotations = actual_total_annotations

View on GitHub (pinned to 0b49e9b539)

Solutions

  1. Fix the annotation objects listed in the error payload so they pass AnnotationSerializer validation (valid 'result' structure, correct data types, valid completed_by user id)
  2. Run AnnotationSerializer(data=annotation).is_valid() locally on the failing object to see field-level messages
  3. If the annotations are intentionally not-yet-valid drafts, move them to a different field (e.g. 'drafts') instead of 'annotations'
  4. Set raise_exception=False if partial import is acceptable and you will inspect logger output

Example fix

// before
task = {"data": {"text": "hi"}, "annotations": [{"result": "not-a-list"}]}
storage.add_task(task, raise_exception=True)
// after
from label_studio.tasks.serializers import AnnotationSerializer
ann = {"result": [{"from_name": "sentiment", "to_name": "text", "type": "choices", "value": {"choices": ["pos"]}}]}
assert AnnotationSerializer(data=ann).is_valid(), AnnotationSerializer(data=ann).errors
storage.add_task({"data": {"text": "hi"}, "annotations": [ann]}, raise_exception=True)
Defensive patterns

Strategy: validation

Validate before calling

from label_studio.tasks.serializers import AnnotationSerializer
def annotations_are_valid(task_payload):
    for ann in task_payload.get('annotations', []):
        ser = AnnotationSerializer(data=ann)
        if not ser.is_valid():
            return False, ser.errors
    return True, None

Type guard

def is_valid_annotation(ann):
    return isinstance(ann, dict) and isinstance(ann.get('result'), list) and all(
        isinstance(r, dict) and 'from_name' in r and 'to_name' in r and 'value' in r for r in ann['result'])

Try / catch

from rest_framework.exceptions import ValidationError
try:
    storage.add_task(task, raise_exception=True)
except ValidationError as e:
    logger.error('Annotation validation failed: %s', e.detail)
    # fix or skip invalid annotations before retrying

Prevention

When it happens

Trigger: Calling add_task (directly or via _scan_and_create_links/create_tasks) with a payload whose 'annotations' contain objects that fail AnnotationSerializer validation (e.g. missing 'result', wrong result type keys, invalid completed_by id, bad draft fields) and raise_exception=True.

Common situations: Importing task JSON exported from another tool with different annotation schema; stale exports from older Label Studio versions with fields renamed; storage sync picking up malformed annotation JSON files; passing string ids where integers are expected.

Related errors


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