apache/superset · error · AmbiguousPurgeTargetError
uuid={self._uuid!r} matches {', '.join(sorted(type(m).__name
Error message
uuid={self._uuid!r} matches {', '.join(sorted(type(m).__name__ for m in matches))}; pass the entity type to disambiguate What it means
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.
Source
Thrown at superset/commands/deletion_retention/force_purge.py:109
"""
candidates = (
[self._model_cls]
if self._model_cls is not None
else SoftDeleteMixin._registered_subclasses # noqa: SLF001
)
matches: list[SoftDeleteMixin] = []
for model in candidates:
if not hasattr(model, "uuid"):
continue
with skip_visibility_filter(db.session, model):
query = db.session.query(model).filter(model.uuid == self._uuid)
if self._require_archived:
query = query.filter(model.deleted_at.is_not(None))
entity = query.first()
if entity is not None:
matches.append(entity)
if len(matches) > 1:
raise AmbiguousPurgeTargetError(
f"uuid={self._uuid!r} matches "
f"{', '.join(sorted(type(m).__name__ for m in matches))}; "
"pass the entity type to disambiguate"
)
return matches[0] if matches else None
def run(self) -> dict[str, Any]:
"""Resolve + purge. Returns a summary; a no-op when nothing matches."""
audit.reconcile_pending()
entity = self._resolve()
if entity is None:
logger.info("force_purge: no entity for uuid=%s (no-op)", self._uuid)
return {"purged": False, "reason": "not_found", "uuid": self._uuid}
entity_type = str(cast(Any, type(entity)).__tablename__)
removed_dashboard_slices = dashboard_slice_count(db.session, entity)
# The audit row commits independently. Release the resolving read
# transaction first, then resolve again against post-audit state.View on GitHub (pinned to f4587218dd)
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.
Example fix
# before ForcePurgeCommand(uuid="abc-...").run() # raises: uuid=... matches Chart, Dashboard; pass the entity type to disambiguate # after ForcePurgeCommand(uuid="abc-...", entity_type="dashboard").run()
Defensive patterns
Strategy: validation
Validate before calling
matches = {type(m).__name__ for m in query_all_models_by_uuid(u) if m}
if len(matches) > 1:
require_entity_type_param() # refuse to purge ambiguously Try / catch
try:
ForcePurgeCommand(uuid=u).run()
except AmbiguousPurgeTargetError as ex:
kind = ask_user_or_policy(str(ex)) # message lists candidate types
ForcePurgeCommand(uuid=u, entity_type=kind).run() Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Dashboard was deleted and re-import requires can_write permi
- Cannot delete a database whose only remaining datasets are s
- No dashboard found for uuid ${sourceUuid}
- A chart already exists and user doesn't have permissions to
- Chart {existing.slice_name!r} (uuid {config['uuid']}) was de
AI-assisted analysis of apache/superset@f4587218dd (2026-08-14).
Data as JSON: /api/errors/74d1594287ddc8a5.
Report an issue: GitHub.