{"record":{"id":"a77e566d63a80d7f","repo":"infiniflow/ragflow","slug":"duplicate-uuids","errorCode":"duplicate_uuids","errorMessage":"Duplicate ids: '{duplicate_ids}'","messagePattern":"Duplicate ids: '(.+?)'","errorType":"validation","errorClass":"PydanticCustomError","httpStatus":null,"severity":"error","filePath":"api/utils/validation_utils.py","lineNumber":938,"sourceCode":"        Security Notes:\n            - Validates UUID format (any version)\n            - Duplicate check prevents data injection\n            - None handling maintains pipeline integrity\n        \"\"\"\n        if v_list is None:\n            return None\n\n        ids_list = []\n        for v in v_list:\n            try:\n                ids_list.append(validate_uuid1_hex(v))\n            except PydanticCustomError as e:\n                raise e\n\n        duplicates = [item for item, count in Counter(ids_list).items() if count > 1]\n        if duplicates:\n            duplicates_str = \", \".join(duplicates)\n            raise PydanticCustomError(\"duplicate_uuids\", \"Duplicate ids: '{duplicate_ids}'\", {\"duplicate_ids\": duplicates_str})\n\n        return ids_list\n\n\nclass DeleteDatasetReq(DeleteReq):\n    \"\"\"Request model for deleting datasets.\"\"\"\n\n    ...\n\n\nclass DeleteDocumentReq(DeleteReq):\n    \"\"\"Request model for deleting documents.\"\"\"\n\n    @field_validator(\"ids\", mode=\"after\")\n    @classmethod\n    def validate_ids(cls, v_list: list[str] | None) -> list[str] | None:\n        \"\"\"\n        Validate document IDs without enforcing UUIDv1.","sourceCodeStart":920,"sourceCodeEnd":956,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/api/utils/validation_utils.py#L920-L956","documentation":"PydanticCustomError (code duplicate_uuids) from DeleteReq.validate_ids (api/utils/validation_utils.py:869-940), the base class for DeleteDatasetReq/DeleteDocumentReq batch-delete payloads. After each entry passes UUID validation it is normalized to a 32-char lowercase hex via validate_uuid1_hex; the validator then counts occurrences with Counter and rejects the request if any normalized id appears more than once. Duplicates are reported back verbatim in the message so the caller sees exactly which ids collide.","triggerScenarios":"DELETE /api/v1/datasets or /api/v1/documents with an ids array containing the same UUID twice — including cases where the two strings differ only in case or hyphenation (e.g. 'ABC...' vs 'abc...', hyphenated vs bare hex) because normalization happens before the duplicate check.","commonSituations":"Front-end accumulating selection across pages and adding the same row twice; scripts concatenating id lists without dedup; mixed-format UUIDs (upper/lower case, with/without dashes) coming from different sources that normalize to the same id.","solutions":["Deduplicate the ids array client-side before sending (use set() or dict.fromkeys to preserve order).","Normalize ids to lowercase 32-char hex before comparing so case/hyphen variants collapse.","Inspect the '{duplicate_ids}' value in the error message to find which entry was repeated."],"exampleFix":"# before\npayload = {\"ids\": selected_ids}  # may contain repeats\n# after\npayload = {\"ids\": list(dict.fromkeys(selected_ids))}","handlingStrategy":"validation","validationCode":"import uuid\nnormalized = [uuid.UUID(x).hex for x in ids]\nif len(set(normalized)) != len(normalized):\n    raise ValueError(\"duplicate dataset/document ids after normalization\")","typeGuard":"def has_unique_uuid_ids(ids: list[str] | None) -> bool:\n    if ids is None:\n        return True\n    seen = set()\n    for x in ids:\n        h = uuid.UUID(x).hex\n        if h in seen:\n            return False\n        seen.add(h)\n    return True","tryCatchPattern":"try:\n    req = DeleteDatasetReq(ids=ids)\nexcept ValidationError as e:\n    if any(err[\"type\"] == \"duplicate_uuids\" for err in e.errors()):\n        ids = list(dict.fromkeys(ids))  # dedup and retry once\n        req = DeleteDatasetReq(ids=ids)\n    else:\n        raise","preventionTips":["Normalize UUIDs to lowercase hex before comparing/deduplicating.","Deduplicate with dict.fromkeys to preserve order.","When merging id lists from multiple sources, dedup as the final build step."],"tags":["validation","pydantic","duplicate-ids","delete-api"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}