{"record":{"id":"c26960993fb5ff81","repo":"apache/superset","slug":"annotation-layer-not-found-c26960","errorCode":null,"errorMessage":"Annotation layer not found.","messagePattern":"Annotation layer not found\\.","errorType":"exception","errorClass":"AnnotationLayerNotFoundError","httpStatus":404,"severity":"error","filePath":"superset/commands/annotation_layer/delete.py","lineNumber":49,"sourceCode":"logger = logging.getLogger(__name__)\n\n\nclass DeleteAnnotationLayerCommand(BaseCommand):\n    def __init__(self, model_ids: list[int]):\n        self._model_ids = model_ids\n        self._models: Optional[list[AnnotationLayer]] = None\n\n    @transaction(on_error=partial(on_error, reraise=AnnotationLayerDeleteFailedError))\n    def run(self) -> None:\n        self.validate()\n        assert self._models\n        AnnotationLayerDAO.delete(self._models)\n\n    def validate(self) -> None:\n        # Validate/populate model exists\n        self._models = AnnotationLayerDAO.find_by_ids(self._model_ids)\n        if not self._models or len(self._models) != len(self._model_ids):\n            raise AnnotationLayerNotFoundError()\n        if AnnotationLayerDAO.has_annotations(self._model_ids):\n            raise AnnotationLayerDeleteIntegrityError()\n","sourceCodeStart":31,"sourceCodeEnd":52,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/commands/annotation_layer/delete.py#L31-L52","documentation":"AnnotationLayerNotFoundError from DeleteAnnotationLayerCommand.validate() (delete.py:49): AnnotationLayerDAO.find_by_ids returned fewer layers than the requested ids — at least one layer id in the bulk DELETE does not exist. HTTP 404; the entire batch is rejected before the integrity check runs.","triggerScenarios":"DELETE /api/v1/annotation_layer/ with [1,2,77] where 77 was already deleted; re-sending a bulk delete; deleting from a stale list after another admin removed a layer.","commonSituations":"Non-idempotent delete scripts; concurrent deletes from two sessions; environment id drift in automation.","solutions":["Filter the id list to layers that still exist before deleting (find_by_ids / GET filter).","Treat 404 on delete as already-done in clients.","Re-fetch the layer list in the UI immediately before a bulk delete action."],"exampleFix":"# before\nDeleteAnnotationLayerCommand([1, 2, 77]).run()\n\n# after\nexisting = AnnotationLayerDAO.find_by_ids([1, 2, 77])\nif existing:\n    DeleteAnnotationLayerCommand([m.id for m in existing]).run()","handlingStrategy":"validation","validationCode":"from superset.daos.annotation_layer import AnnotationLayerDAO\n\nlive = [m.id for m in AnnotationLayerDAO.find_by_ids(ids)]\n# delete only live ids; missing ids are already gone","typeGuard":null,"tryCatchPattern":"try:\n    DeleteAnnotationLayerCommand(ids).run()\nexcept AnnotationLayerNotFoundError:\n    log.info(\"some layers already deleted: %s\", ids)  # idempotent success","preventionTips":["Treat layer-delete 404 as already-deleted in clients.","Re-fetch the layer list before bulk delete actions.","Filter bulk payloads through an existence check."],"tags":["annotation-layer","not-found","bulk-delete"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}