{"record":{"id":"74d1594287ddc8a5","repo":"apache/superset","slug":"uuid-self-uuid-r-matches-join-sorted-type","errorCode":null,"errorMessage":"uuid={self._uuid!r} matches {', '.join(sorted(type(m).__name__ for m in matches))}; pass the entity type to disambiguate","messagePattern":"uuid=(.+?) matches (.+?); pass the entity type to disambiguate","errorType":"exception","errorClass":"AmbiguousPurgeTargetError","httpStatus":null,"severity":"error","filePath":"superset/commands/deletion_retention/force_purge.py","lineNumber":109,"sourceCode":"        \"\"\"\n        candidates = (\n            [self._model_cls]\n            if self._model_cls is not None\n            else SoftDeleteMixin._registered_subclasses  # noqa: SLF001\n        )\n        matches: list[SoftDeleteMixin] = []\n        for model in candidates:\n            if not hasattr(model, \"uuid\"):\n                continue\n            with skip_visibility_filter(db.session, model):\n                query = db.session.query(model).filter(model.uuid == self._uuid)\n                if self._require_archived:\n                    query = query.filter(model.deleted_at.is_not(None))\n                entity = query.first()\n            if entity is not None:\n                matches.append(entity)\n        if len(matches) > 1:\n            raise AmbiguousPurgeTargetError(\n                f\"uuid={self._uuid!r} matches \"\n                f\"{', '.join(sorted(type(m).__name__ for m in matches))}; \"\n                \"pass the entity type to disambiguate\"\n            )\n        return matches[0] if matches else None\n\n    def run(self) -> dict[str, Any]:\n        \"\"\"Resolve + purge. Returns a summary; a no-op when nothing matches.\"\"\"\n        audit.reconcile_pending()\n        entity = self._resolve()\n        if entity is None:\n            logger.info(\"force_purge: no entity for uuid=%s (no-op)\", self._uuid)\n            return {\"purged\": False, \"reason\": \"not_found\", \"uuid\": self._uuid}\n\n        entity_type = str(cast(Any, type(entity)).__tablename__)\n        removed_dashboard_slices = dashboard_slice_count(db.session, entity)\n        # The audit row commits independently. Release the resolving read\n        # transaction first, then resolve again against post-audit state.","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/commands/deletion_retention/force_purge.py#L91-L127","documentation":"AmbiguousPurgeTargetError is raised by the force-purge command's _resolve() when a single uuid matches more than one soft-delete entity type (it queries every candidate model with skip_visibility_filter and deleted_at filters). The message lists the matched type names and instructs you to pass the entity type to disambiguate, since Superset uuids are only unique per model class, not globally.","triggerScenarios":"Invoking the force-purge flow by uuid alone when the same uuid exists on e.g. both a Dashboard and a Chart (copied/duplicated objects can share uuids across types after export/import or manual writes). With _require_archived, matches are restricted to soft-deleted rows, but two different archived models can still collide.","commonSituations":"Export/import of dashboards that re-created objects with existing uuids. Duplicate-then-delete workflows that copy a uuid onto another entity type. Automated cleanup scripts that purge by uuid harvested from audit logs without recording the entity kind.","solutions":["Pass the entity type (e.g. 'dashboard' / 'chart') to the force-purge call so _resolve only queries that model — exactly what the message suggests.","If unsure which entity is intended, inspect each matched object first (query by uuid per model) and pick by content.","Fix the underlying data: re-uuid the wrongly duplicated entity so future purges are unambiguous.","Record entity kind alongside uuid in your automation's delete queues."],"exampleFix":"# before\nForcePurgeCommand(uuid=\"abc-...\").run()\n# raises: uuid=... matches Chart, Dashboard; pass the entity type to disambiguate\n\n# after\nForcePurgeCommand(uuid=\"abc-...\", entity_type=\"dashboard\").run()","handlingStrategy":"validation","validationCode":"matches = {type(m).__name__ for m in query_all_models_by_uuid(u) if m}\nif len(matches) > 1:\n    require_entity_type_param()  # refuse to purge ambiguously","typeGuard":null,"tryCatchPattern":"try:\n    ForcePurgeCommand(uuid=u).run()\nexcept AmbiguousPurgeTargetError as ex:\n    kind = ask_user_or_policy(str(ex))  # message lists candidate types\n    ForcePurgeCommand(uuid=u, entity_type=kind).run()","preventionTips":["Always record (uuid, entity_type) pairs in deletion queues.","Never copy uuids between objects of different models.","When the message fires, let it name the types; disambiguate deliberately, not by guessing."],"tags":["purge","uuid","ambiguity","soft-delete","cleanup"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}