{"record":{"id":"9e6482986fce6705","repo":"ArchiveBox/ArchiveBox","slug":"tag-existing-name-already-exists","errorCode":null,"errorMessage":"Tag \"{existing.name}\" already exists","messagePattern":"Tag \"(.+?)\" already exists","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"archivebox/core/tag_util.py","lineNumber":173,"sourceCode":"    existing = Tag.objects.filter(name__iexact=normalized_name).first()\n    if existing:\n        return existing, False\n\n    tag = Tag.objects.create(\n        name=normalized_name,\n        created_by=created_by,\n    )\n    return tag, True\n\n\ndef rename_tag(tag: Tag, name: str) -> Tag:\n    normalized_name = normalize_tag_name(name)\n    if not normalized_name:\n        raise ValueError(\"Tag name is required\")\n\n    existing = Tag.objects.filter(name__iexact=normalized_name).exclude(pk=tag.pk).first()\n    if existing:\n        raise ValueError(f'Tag \"{existing.name}\" already exists')\n\n    if tag.name != normalized_name:\n        tag.name = normalized_name\n        tag.save()\n    return tag\n\n\ndef delete_tag(tag: Tag) -> tuple[int, dict[str, int]]:\n    return tag.delete()\n\n\ndef export_tag_urls(tag: Tag) -> str:\n    urls = tag.snapshot_set.order_by(\"-downloaded_at\", \"-created_at\", \"-pk\").values_list(\"url\", flat=True)\n    return \"\\n\".join(urls)\n\n\ndef export_tag_snapshots_jsonl(tag: Tag) -> str:\n    snapshots = tag.snapshot_set.order_by(\"-downloaded_at\", \"-created_at\", \"-pk\").prefetch_related(\"tags\")","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/ArchiveBox/ArchiveBox/blob/74564b28220090664f919479e82cbf454125fa34/archivebox/core/tag_util.py#L155-L191","documentation":"rename_tag raises ValueError when another tag (different primary key) already exists with a case-insensitively equal name after normalization. This guards Tag.name uniqueness when renaming, since Django's unique constraint alone can miss case-variant duplicates. It fires before any rename is applied, so state is unchanged.","triggerScenarios":"Calling rename_tag(tag, name) where normalize_tag_name(name) matches (case-insensitively) the name of a different Tag row, e.g. renaming 'News' to 'news' when a tag 'news' already exists.","commonSituations":"Users rename a tag to a capitalized/lowercased variant of an existing tag; case-insensitive DB collations or prior imports created near-duplicate tags; admin/API flows allow overlapping names.","solutions":["Choose a different target name that does not collide (case-insensitively) with any existing tag.","Merge the two tags first (move all Snapshot-tag links onto the existing tag, delete the duplicate), then rename if still needed.","Query Tag.objects.filter(name__iexact=normalize_tag_name(new_name)).exclude(pk=tag.pk) before calling rename_tag to pre-check collisions."],"exampleFix":"// before\nrename_tag(tag, \"News\")  # ValueError: Tag \"news\" already exists\n// after\nexisting = Tag.objects.get(name__iexact=\"news\")\nfor snap in tag.snapshot_set.all():\n    existing.snapshot_set.add(snap)\ntag.snapshot_set.clear()\ntag.delete()","handlingStrategy":"validation","validationCode":"from archivebox.core.tag_util import normalize_tag_name\nnew_name = normalize_tag_name(candidate)\ncollision = Tag.objects.filter(name__iexact=new_name).exclude(pk=tag.pk).exists()\nif not new_name or collision:\n    # pick another name or merge instead of renaming\n    ...","typeGuard":null,"tryCatchPattern":"try:\n    rename_tag(tag, new_name)\nexcept ValueError as e:\n    if \"already exists\" in str(e):\n        merge_into_existing(tag, Tag.objects.get(name__iexact=normalize_tag_name(new_name)))\n    else:\n        raise","preventionTips":["Always normalize the name with normalize_tag_name before suggesting a rename.","Pre-check case-insensitive collisions with Tag.objects.filter(name__iexact=...).","Prefer merging duplicate tags over creating case-variant names."],"tags":["django","validation","tags","duplicate"],"backgroundTag":"duplicate-tag-name","analyzedSha":"74564b28220090664f919479e82cbf454125fa34","analyzedAt":"2026-08-28T23:56:51.556Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}