{"record":{"id":"d27b5d3baae7187a","repo":"apache/superset","slug":"report-schedule-not-found","errorCode":null,"errorMessage":"Report Schedule not found.","messagePattern":"Report Schedule not found\\.","errorType":"exception","errorClass":"ReportScheduleNotFoundError","httpStatus":404,"severity":"error","filePath":"superset/commands/report/delete.py","lineNumber":51,"sourceCode":"logger = logging.getLogger(__name__)\n\n\nclass DeleteReportScheduleCommand(BaseCommand):\n    def __init__(self, model_ids: list[int]):\n        self._model_ids = model_ids\n        self._models: Optional[list[ReportSchedule]] = None\n\n    @transaction(on_error=partial(on_error, reraise=ReportScheduleDeleteFailedError))\n    def run(self) -> None:\n        self.validate()\n        assert self._models\n        ReportScheduleDAO.delete(self._models)\n\n    def validate(self) -> None:\n        # Validate/populate model exists\n        self._models = ReportScheduleDAO.find_by_ids(self._model_ids)\n        if not self._models or len(self._models) != len(self._model_ids):\n            raise ReportScheduleNotFoundError()\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 ReportScheduleForbiddenError() from ex\n","sourceCodeStart":33,"sourceCodeEnd":59,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/commands/report/delete.py#L33-L59","documentation":"ReportScheduleNotFoundError from DeleteReportCommand.validate: the command resolves every id in _model_ids via ReportScheduleDAO.find_by_ids and requires the result count to equal the request count. Any missing, already-deleted, or access-filtered id makes the lists mismatch and the delete aborts.","triggerScenarios":"DELETE /api/v1/report/ with a list where at least one id does not exist (deleted moments before, wrong id, or filtered by row-level rules). Also when two concurrent deletes race and one removes the row first.","commonSituations":"Frontend double-submit of a delete button; cleanup scripts holding stale ids; another admin deleted the report between listing and deleting.","solutions":["GET /api/v1/report/{id} first to confirm existence, or filter your id list against current rows","Treat 404 as success in idempotent cleanup scripts (the end state — report absent — is achieved)","Guard the UI against double-delete submissions"],"exampleFix":"# before\nif not sm.get_by_id(report_id):\n    pass  # fall through and still try to delete\nDeleteReportCommand(user, [report_id]).run()\n\n# after\nif ReportScheduleDAO.find_by_id(report_id) is None:\n    return  # already gone; nothing to do\nDeleteReportCommand(user, [report_id]).run()","handlingStrategy":"type-guard","validationCode":"from superset.reports.dao import ReportScheduleDAO\n\nids = [i for i in requested_ids if ReportScheduleDAO.find_by_id(i) is not None]\nif not ids:\n    return  # nothing deletable; already gone","typeGuard":"def deletable_ids(ids: list[int]) -> list[int]:\n    found = {m.id for m in ReportScheduleDAO.find_by_ids(ids) or []}\n    return [i for i in ids if i in found]","tryCatchPattern":"try:\n    DeleteReportCommand(user, ids).run()\nexcept ReportScheduleNotFoundError:\n    pass  # treat as success in idempotent cleanup","preventionTips":["Make delete flows idempotent: 404 means the desired end state already holds","Filter id batches through a find_by_ids call before deleting","Debounce delete buttons to avoid double-submit races"],"tags":["alerts-reports","not-found","delete","api"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}