{"record":{"id":"b8380cbb6c1423bc","repo":"apache/superset","slug":"dashboard-cannot-be-restored-via-re-import-because","errorCode":null,"errorMessage":"Dashboard cannot be restored via re-import because its slug {effective_slug!r} is now used by another active dashboard. Upload a YAML with a different slug, or rename the active dashboard, and retry.","messagePattern":"Dashboard cannot be restored via re-import because its slug (.+?) is now used by another active dashboard\\. Upload a YAML with a different slug, or rename the active dashboard, and retry\\.","errorType":"exception","errorClass":"ImportFailedError","httpStatus":500,"severity":"error","filePath":"superset/commands/dashboard/importers/v1/utils.py","lineNumber":389,"sourceCode":"            # config as field updates on the existing object, preserving the PK.\n            # Same active-slug-twin check as the explicit restore\n            # (``RestoreDashboardCommand``): the common re-import carries the\n            # pre-deletion slug, which another active dashboard may have\n            # claimed during the soft-deleted window. Check the *effective*\n            # post-restore slug BEFORE mutating the row: the validation query\n            # triggers the session's autoflush, so if the row were already\n            # marked restored, the flush would emit the ``deleted_at = NULL``\n            # UPDATE mid-validation and hit the partial unique index as an\n            # opaque IntegrityError-wrapped import failure on exactly the\n            # dialects (Postgres / MySQL 8.0.13+) this readable error exists\n            # for. Checking first also leaves the row untouched on failure.\n            effective_slug = config.get(\"slug\", existing.slug)\n            if effective_slug is not None and not (\n                DashboardDAO.validate_update_slug_uniqueness(\n                    existing.id, effective_slug\n                )\n            ):\n                raise ImportFailedError(\n                    f\"Dashboard cannot be restored via re-import because its \"\n                    f\"slug {effective_slug!r} is now used by another active \"\n                    f\"dashboard. Upload a YAML with a different slug, or \"\n                    f\"rename the active dashboard, and retry.\"\n                )\n            existing.restore()\n            # Apply the incoming slug to the existing row before flushing. On\n            # the partial-index dialects (Postgres / MySQL 8.0.13+) the\n            # active-slug constraint sees the row's post-flush state. If the old\n            # slug was claimed by another active dashboard while this one was\n            # soft-deleted, the operator resolves it by uploading a YAML with a\n            # different (safe) slug — the flush below must reflect that change,\n            # or the implicit-restore path fails on the stale DB slug even\n            # though the upload was supposed to fix it.\n            if \"slug\" in config:\n                existing.slug = config[\"slug\"]\n            db.session.flush()\n            config[\"id\"] = existing.id","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/commands/dashboard/importers/v1/utils.py#L371-L407","documentation":"ImportFailedError is raised in the v1 restore branch when the effective slug (from the bundle's config, falling back to the existing row's slug) is now claimed by another active dashboard, per DashboardDAO.validate_update_slug_uniqueness. A partial unique index on active dashboards' slugs would otherwise turn the restore into an opaque IntegrityError on Postgres/MySQL 8.0.13+, so the check runs before the row is touched, leaving it untouched on failure.","triggerScenarios":"Re-importing a soft-deleted dashboard whose slug was re-used by a new active dashboard: another team created a dashboard with the same slug between the soft-delete and the re-import attempt.","commonSituations":"Deleted dashboards whose slugs were later reclaimed; environment sync pipelines re-pushing old bundles into an environment where the short slug was taken; copy workflows that reuse human-friendly slugs like 'sales'.","solutions":["Edit the bundle's dashboard YAML and set a different, unused slug, then re-zip and re-import.","Or rename the active dashboard currently holding the slug (or clear its slug), then retry the import unchanged.","Or omit the slug in the bundle only if the existing soft-deleted row's slug is itself free — otherwise one of the two rows must change."],"exampleFix":"# before\n# bundle YAML: slug: sales  (active dashboard 'Sales FY26' already owns 'sales')\nclient.post('/api/v1/dashboard/import/', ...)\n\n# after\n# dashboard_DATA.yaml -> slug: sales-legacy\n# re-zip and import; or PUT /api/v1/dashboard/{active_id} json={'slug': 'sales-fy26'} first","handlingStrategy":"validation","validationCode":"from superset.daos.dashboard import DashboardDAO\n\neffective_slug = bundle_config.get('slug') or existing.slug\nif effective_slug and not DashboardDAO.validate_update_slug_uniqueness(existing.id, effective_slug):\n    bundle_config['slug'] = f\"{effective_slug}-restored-{uuid4().hex[:4]}\"\n    repackage_bundle()","typeGuard":null,"tryCatchPattern":"try:\n    run_import(bundle)\nexcept ImportFailedError as ex:\n    if 'slug' in str(ex) and 'another active dashboard' in str(ex):\n        rewrite_bundle_slug_and_retry()","preventionTips":["Namespace slugs per team/purpose (e.g. 'sales-fy25-legacy') to avoid collisions.","Before restoring by import, check the slug is free with validate_update_slug_uniqueness.","When reusing short slugs, expect conflicts and automate slug rewriting in pipelines."],"tags":["import","restore","slug","unique-constraint","conflict"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}