{"record":{"id":"22cb9fa45401fd7b","repo":"HumanSignal/label-studio","slug":"annotation-ser-errors","errorCode":null,"errorMessage":"annotation_ser.errors","messagePattern":"annotation_ser\\.errors","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"label_studio/io_storages/base_models.py","lineNumber":626,"sourceCode":"                # ignores export identity fields; reusing exported unique_id violates the\n                # DB unique constraint. Map export id -> import_id for parity with bulk import.\n                annotation.pop('unique_id', None)\n                export_id = annotation.pop('id', None)\n                if export_id is not None and annotation.get('import_id') is None:\n                    annotation['import_id'] = export_id\n                annotation['task'] = task.id\n                annotation['project'] = project.id\n            annotation_ser = AnnotationSerializer(data=annotations, many=True)\n\n            # Always validate annotations, but control error handling based on FF\n            created_annotations = []\n            if annotation_ser.is_valid():\n                created_annotations = annotation_ser.save()\n            else:\n                # Log validation errors but don't save invalid annotations\n                logger.error(f'Invalid annotations for task {task.id}: {annotation_ser.errors}')\n                if raise_exception:\n                    raise ValidationError(annotation_ser.errors)\n\n            # Reconcile the denormalized task counters with what actually persisted. The task\n            # above is seeded with counts taken from the *payload* annotations/predictions, but\n            # rows can be silently skipped when invalid (under\n            # ff_fix_back_dev_3342_storage_scan_with_invalid_annotations, is_valid() fails and we\n            # don't raise). Without this, a task whose only annotation was skipped keeps\n            # total_annotations=1 while having zero annotation rows — a stale counter the Data\n            # Manager reads directly (per-task column and tab totals). Recompute from the rows we\n            # actually created so the cached counters can't drift above reality.\n            actual_total_annotations = sum(1 for a in created_annotations if not a.was_cancelled)\n            actual_cancelled_annotations = sum(1 for a in created_annotations if a.was_cancelled)\n            actual_total_predictions = len(created_predictions)\n            if (\n                task.total_annotations != actual_total_annotations\n                or task.cancelled_annotations != actual_cancelled_annotations\n                or task.total_predictions != actual_total_predictions\n            ):\n                task.total_annotations = actual_total_annotations","sourceCodeStart":608,"sourceCodeEnd":644,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/io_storages/base_models.py#L608-L644","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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)","Run AnnotationSerializer(data=annotation).is_valid() locally on the failing object to see field-level messages","If the annotations are intentionally not-yet-valid drafts, move them to a different field (e.g. 'drafts') instead of 'annotations'","Set raise_exception=False if partial import is acceptable and you will inspect logger output"],"exampleFix":"// before\ntask = {\"data\": {\"text\": \"hi\"}, \"annotations\": [{\"result\": \"not-a-list\"}]}\nstorage.add_task(task, raise_exception=True)\n// after\nfrom label_studio.tasks.serializers import AnnotationSerializer\nann = {\"result\": [{\"from_name\": \"sentiment\", \"to_name\": \"text\", \"type\": \"choices\", \"value\": {\"choices\": [\"pos\"]}}]}\nassert AnnotationSerializer(data=ann).is_valid(), AnnotationSerializer(data=ann).errors\nstorage.add_task({\"data\": {\"text\": \"hi\"}, \"annotations\": [ann]}, raise_exception=True)","handlingStrategy":"validation","validationCode":"from label_studio.tasks.serializers import AnnotationSerializer\ndef annotations_are_valid(task_payload):\n    for ann in task_payload.get('annotations', []):\n        ser = AnnotationSerializer(data=ann)\n        if not ser.is_valid():\n            return False, ser.errors\n    return True, None","typeGuard":"def is_valid_annotation(ann):\n    return isinstance(ann, dict) and isinstance(ann.get('result'), list) and all(\n        isinstance(r, dict) and 'from_name' in r and 'to_name' in r and 'value' in r for r in ann['result'])","tryCatchPattern":"from rest_framework.exceptions import ValidationError\ntry:\n    storage.add_task(task, raise_exception=True)\nexcept ValidationError as e:\n    logger.error('Annotation validation failed: %s', e.detail)\n    # fix or skip invalid annotations before retrying","preventionTips":["Validate annotations with AnnotationSerializer.is_valid() before bulk import","Ensure completed_by references existing user ids","Keep export files within the same Label Studio schema version","Prefer passing drafts separately instead of malformed annotations"],"tags":["django","drf","validation","import"],"backgroundTag":"serializer-validation-failed","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}