{"record":{"id":"37dca607bd5f9f49","repo":"HumanSignal/label-studio","slug":"these-labels-still-exist-in-annotations-or-drafts","errorCode":null,"errorMessage":"These labels still exist in annotations or drafts:\\n{diff_str}Please add labels to tag with name=\"{control_tag_from_data}\".","messagePattern":"These labels still exist in annotations or drafts:\\\\n(.+?)Please add labels to tag with name=\"(.+?)\"\\.","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"label_studio/projects/models.py","lineNumber":805,"sourceCode":"                    annotation_label_count = self.summary.created_labels.get(control_tag_from_data, {}).get(label, 0)\n                    draft_label_count = self.summary.created_labels_drafts.get(control_tag_from_data, {}).get(label, 0)\n                    annotation_display_count = display_count(annotation_label_count, 'annotation')\n                    draft_display_count = display_count(draft_label_count, 'draft')\n\n                    display = [disp for disp in [annotation_display_count, draft_display_count] if disp]\n                    if display:\n                        diff_str += f'{label} ({\", \".join(display)})\\n'\n\n                if (strict is True) and (\n                    (control_tag_from_data not in dynamic_label_from_config)\n                    and (\n                        not check_control_in_config_by_regex(\n                            config_string, control_tag_from_data, filter=dynamic_label_from_config.keys()\n                        )\n                    )\n                ):\n                    # raise error if labels not dynamic and not in regex rules\n                    raise ValidationError(\n                        f'These labels still exist in annotations or drafts:\\n{diff_str}'\n                        f'Please add labels to tag with name=\"{str(control_tag_from_data)}\".'\n                    )\n                else:\n                    logger.info(f'project_id={self.id} inconsistent labels in config and annotations: {diff_str}')\n\n    def _label_config_has_changed(self):\n        return self.label_config != self.__original_label_config\n\n    @property\n    def label_config_is_not_default(self):\n        return self.label_config != Project._meta.get_field('label_config').default\n\n    def should_none_model_version(self, model_version):\n        \"\"\"\n        Returns True if the model version provided matches the object's model version,\n        or no model version is set for the object but model version exists in ML backend.\n        \"\"\"","sourceCodeStart":787,"sourceCodeEnd":823,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/projects/models.py#L787-L823","documentation":"After checking that the control tag still exists, Label Studio verifies that labels recorded in existing annotations or drafts still appear in the new config's label set. If specific labels (choices/values) used in annotations were removed from the tag and are neither dynamic nor matched by regex rules, this ValidationError lists the offending labels in diff_str.","triggerScenarios":"POST /api/projects/<id>/validate-label-config or project save where annotation/draft results contain labels (e.g. choice value 'Maybe') that are missing from the corresponding control tag in the new config and dynamic_label_from_config does not cover them.","commonSituations":"Trimming a list of <Choice> options from 5 to 3 after annotation has begun; renaming <Label> values in a segmentation template; replacing a taxonomy with a shorter one.","solutions":["Re-add the missing labels to the control tag in the config","Convert the tag to dynamic labeling (dynamic labels / regex) so stored labels remain valid","Delete or rewrite the annotations/drafts that use the removed labels","Archive the old data and start a fresh project with the trimmed label set"],"exampleFix":"// before\n<Choices name='sentiment' toName='text'>\n  <Choice value='Positive'/><Choice value='Negative'/>\n</Choices>\n// after (re-add label still present in annotations)\n<Choices name='sentiment' toName='text'>\n  <Choice value='Positive'/><Choice value='Negative'/><Choice value='Neutral'/>\n</Choices>","handlingStrategy":"validation","validationCode":"// Compare labels used in annotations against labels declared in the new config\nconst annotations = await api.get('/api/annotations', {params:{project: projectId}});\nconst usedLabels = new Set(annotations.results.flatMap(a =>\n  a.result.flatMap(r => Object.values(r.value ?? {}).flat())));\nconst declaredLabels = new Set([...newConfig.matchAll(/value=\"([^\"]+)\"/g)].map(m => m[1]));\nconst missing = [...usedLabels].filter(l => !declaredLabels.has(l));\nif (missing.length) throw new Error(`Add missing labels to config: ${missing.join(', ')}`);","typeGuard":"function allUsedLabelsDeclared(used, declared) {\n  return used.every(l => declared.has(l));\n}","tryCatchPattern":"try {\n  await projectApi.update(projectId, {label_config: newConfig});\n} catch (e) {\n  if (e.response?.status === 400 && /labels still exist in annotations or drafts/.test(JSON.stringify(e.response.data))) {\n    // parse listed labels from the message and re-add them or migrate annotations\n  } else throw e;\n}","preventionTips":["Only remove label values after confirming no annotation/draft references them","Run validate-label-config before every config save","Use dynamic labels when the label vocabulary is expected to shrink/change","Communicate label-set changes to annotation teams and migrate data first"],"tags":["label-studio","validation","labels","labeling-config"],"backgroundTag":"schema-validation-failed","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}