{"record":{"id":"89bf8125f8943eda","repo":"apache/superset","slug":"dashboard-not-found","errorCode":null,"errorMessage":"Dashboard not found.","messagePattern":"Dashboard not found\\.","errorType":"exception","errorClass":"DashboardNotFoundError","httpStatus":404,"severity":"warning","filePath":"superset/commands/dashboard/delete.py","lineNumber":72,"sourceCode":"            raise DashboardForbiddenError() from ex\n\n\nclass DeleteDashboardCommand(BaseCommand):\n    def __init__(self, model_ids: list[int]):\n        self._model_ids = model_ids\n        self._models: Optional[list[Dashboard]] = None\n\n    @transaction(on_error=partial(on_error, reraise=DashboardDeleteFailedError))\n    def run(self) -> None:\n        self.validate()\n        assert self._models\n        DashboardDAO.delete(self._models)\n\n    def validate(self) -> None:\n        # Validate/populate model exists\n        self._models = DashboardDAO.find_by_ids(self._model_ids)\n        if not self._models or len(self._models) != len(self._model_ids):\n            raise DashboardNotFoundError()\n        # Check there are no associated ReportSchedules\n        if reports := ReportScheduleDAO.find_by_dashboard_ids(self._model_ids):\n            report_names = [report.name for report in reports]\n            raise DashboardDeleteFailedReportsExistError(\n                _(\n                    \"There are associated alerts or reports: %(report_names)s\",\n                    report_names=\",\".join(report_names),\n                )\n            )\n        # Check editorship\n        for model in self._models:\n            try:\n                security_manager.raise_for_editorship(model)\n            except SupersetSecurityException as ex:\n                raise DashboardForbiddenError() from ex\n","sourceCodeStart":54,"sourceCodeEnd":88,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/commands/dashboard/delete.py#L54-L88","documentation":"DashboardNotFoundError is raised by DeleteDashboardCommand.validate() when DashboardDAO.find_by_ids() returns no rows or fewer rows than the requested id list. At least one dashboard id in the bulk delete request does not exist (never existed, was hard-deleted, or was deleted concurrently).","triggerScenarios":"DELETE /api/v1/dashboard/ with an id list containing a stale or nonexistent dashboard id; concurrent deletion by another user between listing and deleting; deleting a soft-deleted dashboard whose row the DAO no longer returns.","commonSituations":"Multi-select bulk delete in the UI after another admin already removed one dashboard; retrying a failed bulk delete where part of the batch succeeded; scripts with cached ids after a metadata DB restore.","solutions":["Re-fetch the dashboard list and retry the delete with only ids that still exist.","Treat 404 on delete as success in idempotent automation (the end state — dashboard absent — is achieved).","Split large bulk deletes into per-id deletes so one missing id does not abort the whole batch."],"exampleFix":"# before\nDeleteDashboardCommand([10, 11, 999]).run()  # 999 gone -> whole batch 404s\n\n# after\nexisting = DashboardDAO.find_by_ids([10, 11, 999])\nif existing:\n    DeleteDashboardCommand([d.id for d in existing]).run()","handlingStrategy":"validation","validationCode":"from superset.daos.dashboard import DashboardDAO\n\nmodels = DashboardDAO.find_by_ids(ids)\nalive_ids = [m.id for m in models]\nif len(alive_ids) != len(ids):\n    logger.info('skipping missing ids: %s', set(ids) - set(alive_ids))","typeGuard":null,"tryCatchPattern":"try:\n    DeleteDashboardCommand(ids).run()\nexcept DashboardNotFoundError:\n    # treat as already deleted; optionally narrow and retry\n    retry_with_existing_ids_only()","preventionTips":["Re-query ids right before bulk delete.","Split bulk deletes per id so one stale id cannot abort the batch.","Treat delete-404 as success in idempotent automation."],"tags":["dashboard","delete","not-found","bulk-operations"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}