{"record":{"id":"6e7bd542ae9043f8","repo":"HumanSignal/label-studio","slug":"it-s-expected-to-have-email-field-in-completed","errorCode":null,"errorMessage":"It's expected to have 'email' field in 'completed_by' data in annotations","messagePattern":"It's expected to have 'email' field in 'completed_by' data in annotations","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"label_studio/tasks/serializers.py","lineNumber":598,"sourceCode":"        for annotation in annotations:\n            completed_by = annotation.get('completed_by')\n\n            if use_fallback:\n                resolved_id = resolve_completed_by_id(completed_by, members_email_to_id, members_ids, default_user.id)\n                if resolved_id is not None:\n                    annotation['completed_by_id'] = resolved_id\n                annotation.pop('completed_by', None)\n                continue\n\n            # --- legacy FF-off branches -------------------------------------------------\n            # no completed_by info found - just skip it, will be assigned to the user who imports\n            if completed_by is None:\n                annotation['completed_by_id'] = default_user.id\n\n            # resolve annotators by email\n            elif isinstance(completed_by, dict):\n                if 'email' not in completed_by:\n                    raise ValidationError(\"It's expected to have 'email' field in 'completed_by' data in annotations\")\n\n                email = completed_by['email']\n                if email not in members_email_to_id:\n                    if settings.ALLOW_IMPORT_TASKS_WITH_UNKNOWN_EMAILS:\n                        annotation['completed_by_id'] = default_user.id\n                    else:\n                        raise ValidationError(f\"Unknown annotator's email {email}\")\n                else:\n                    # overwrite an actual member ID\n                    annotation['completed_by_id'] = members_email_to_id[email]\n\n            # old style annotators specification - try to find them by ID\n            elif isinstance(completed_by, int) and completed_by in members_ids:\n                annotation['completed_by_id'] = completed_by\n\n            # in any other cases - import validation error\n            else:\n                raise ValidationError(","sourceCodeStart":580,"sourceCodeEnd":616,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/tasks/serializers.py#L580-L616","documentation":"Raised in BaseTaskSerializerBulk._insert_valid_completed_by when an imported annotation's completed_by is a dict but lacks an 'email' key (legacy FF-off path). completed_by must be either an email dict, a known member ID, or None; the library requires the dict form to carry 'email' so it can map the annotator to a user in the importing organization.","triggerScenarios":"Importing tasks with annotations like {\"completed_by\": {\"id\": 5}} or {\"completed_by\": {\"user\": \"x@y.com\"}} — a dict without 'email' — while feature flag fflag_fix_back_bros_1092_import_unknown_completed_by_short is disabled and create() runs during task import.","commonSituations":"Hand-converted exports where completed_by was reshaped but 'email' renamed or dropped; exports from systems that key annotators by username or ID; migrating annotations between Label Studio instances with customized export scripts.","solutions":["Include the annotator's email in the dict: {\"completed_by\": {\"email\": \"annotator@example.com\"}}","Ensure the annotator email belongs to a member of the target project's organization (register/invite them first)","Or use the legacy integer form with a known member ID: {\"completed_by\": 5}","Or omit completed_by entirely (it defaults to the importing user)","Enable the BROS-1092 fallback feature flag so unknown completed_by values are re-attributed to the default user instead of raising"],"exampleFix":"// before\n{\"annotations\": [{\"result\": [], \"completed_by\": {\"user_id\": 5}}]}\n// after\n{\"annotations\": [{\"result\": [], \"completed_by\": {\"email\": \"annotator@example.com\"}}]}","handlingStrategy":"validation","validationCode":"def check_completed_by(annotations):\n    for a in annotations:\n        cb = a.get('completed_by')\n        if isinstance(cb, dict) and 'email' not in cb:\n            raise ValueError(f\"completed_by dict missing 'email': {cb}\")","typeGuard":"def has_email_completed_by(a):\n    cb = a.get('completed_by')\n    return not isinstance(cb, dict) or 'email' in cb","tryCatchPattern":"from rest_framework.exceptions import ValidationError\ntry:\n    client.import_tasks(project_id, tasks)\nexcept ValidationError as e:\n    if \"expected to have 'email' field\" in str(e.detail):\n        for t in tasks:\n            for a in t.get('annotations', []):\n                cb = a.get('completed_by')\n                if isinstance(cb, dict) and 'email' not in cb:\n                    a['completed_by'] = None  # fall back to importing user\n    else:\n        raise","preventionTips":["Always export annotators as {\"email\": ...} dicts when hand-writing import JSON","Normalize export scripts to emit the email-keyed completed_by form","Prefer omitting completed_by unless correct attribution is required"],"tags":["django","import","annotations","completed-by","validation"],"backgroundTag":"missing-required-field","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}