{"record":{"id":"01fc4c6e86dda9c6","repo":"HumanSignal/label-studio","slug":"error-validating-annotation-validation-errors","errorCode":null,"errorMessage":"Error validating annotation: {validation_errors}","messagePattern":"Error validating annotation: (.+?)","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"label_studio/tasks/serializers.py","lineNumber":236,"sourceCode":"    def validate(self, data):\n        \"\"\"Validate annotation result against project config and custom interface output_schema.\"\"\"\n        if 'result' not in data or data.get('was_cancelled') is True:\n            return super().validate(data)\n\n        project = self._resolve_project_for_validation(data)\n        custom_interface_validator = load_func(getattr(settings, 'CUSTOM_INTERFACE_ANNOTATION_VALIDATOR', None))\n        if custom_interface_validator and project:\n            task = data.get('task') or self.context.get('task') or getattr(self.instance, 'task', None)\n            request = self.context.get('request')\n            user = getattr(request, 'user', None) if request is not None else None\n            # Integrity binds to the annotation author, not the acting user:\n            # a reviewer editing a contributor's annotation must validate\n            # against the contributor's uploads.\n            if self.instance is not None:\n                user = getattr(self.instance, 'completed_by', None) or user\n            validation_errors = custom_interface_validator(project, data.get('result', []), task=task, user=user)\n            if validation_errors:\n                raise ValidationError(f'Error validating annotation: {validation_errors}')\n\n        return super().validate(data)\n\n    def get_created_username(self, annotation) -> str:\n        user = annotation.completed_by\n        if not user:\n            return ''\n\n        request = self.context.get('request')\n        requester = getattr(request, 'user', None) if request is not None else None\n        if AnnotatorReviewerFirewall.should_anonymize(user=user, requester=requester):\n            return AnnotatorReviewerFirewall.role_label(user=user, requester=requester)\n\n        project = self.context.get('project') or getattr(annotation, 'project', None)\n        if is_user_deleted(user, context=self.context, project=project):\n            return f'Deleted User {user.id} deleted-{user.id}-user@example.com, {user.id}'\n\n        name = user.first_name","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/tasks/serializers.py#L218-L254","documentation":"This error is raised by AnnotationSerializer.validate when the project's custom_interface_validator returns non-empty validation errors for the annotation's result data. Label Studio allows projects to define a custom validation interface that checks annotation results (e.g., per-region constraints) before saving; any findings are surfaced as a single ValidationError wrapping the validator's messages.","triggerScenarios":"POST/PATCH to the annotations API (/api/tasks/<id>/annotations/ or /api/annotations/<id>/) where project.build_url or project settings define a custom_interface_validator and the submitted 'result' array violates the project's custom validation rules. Note: for a reviewer editing an existing annotation, validation is run against the original contributor (instance.completed_by) rather than the reviewer.","commonSituations":"Custom label config changed after annotations were submitted so old results no longer validate; client plugins/scripts producing results that don't match the custom validator's expectations; reviewers editing annotations and the validator checking the contributor's uploads; exporting/re-importing annotations with fields the custom validator rejects.","solutions":["Inspect the {validation_errors} payload in the message to see exactly which results/regions failed the custom interface validator","Compare the submitted 'result' array against the project's current labeling config and fix the offending result items before resubmitting","If a reviewer is editing, verify the original contributor's uploads satisfy the validator — the validator runs as the contributor, not the reviewer","If the custom validator logic itself is wrong (e.g., stale rules after a config change), update or relax the project's custom interface validator","As a workaround for bulk data, disable or adjust the custom validation interface for the project in Settings"],"exampleFix":"// before\nconst payload = { result: [{ from_name: 'label', value: { labels: ['wrong_label'] } }] };\n// after\nconst payload = { result: [{ from_name: 'label', value: { labels: ['expected_label'] } }] }; // match custom validator expectations","handlingStrategy":"validation","validationCode":"// client-side pre-check against project constraints\nfunction isAnnotationValid(project, result, user) {\n  if (!Array.isArray(result) || result.length === 0) return false;\n  return result.every(r =>\n    r && typeof r.from_name === 'string' && r.type &&\n    project.labels?.[r.from_name] // from_name must exist in current labeling config\n  );\n}\nif (!isAnnotationValid(project, payload.result, user)) {\n  throw new Error('result violates project custom interface validation');\n}","typeGuard":"function isAnnotationPayload(p) {\n  return typeof p === 'object' && p !== null && Array.isArray(p.result);\n}","tryCatchPattern":"try {\n  const annotation = await api.post(`/api/tasks/${taskId}/annotations/`, payload);\n} catch (e) {\n  if (e.response?.status === 400 && JSON.stringify(e.response.data).includes('Error validating annotation')) {\n    console.warn('Custom validation failed:', e.response.data);\n  } else throw e;\n}","preventionTips":["Keep the labeling config stable or re-validate existing annotations after config changes","Log the full validation_errors payload from the 400 response before retrying","Mirror the custom validator's rules in client-side pre-submit checks","When reviewers edit annotations, remember validation runs against the contributor's context"],"tags":["validation","serializer","annotations","label-studio"],"backgroundTag":"annotation-validation-failed","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}