{"record":{"id":"2b6f0b0547dfcbe2","repo":"apache/superset","slug":"task-task-uuid-is-in-progress-but-has-not-regist","errorCode":null,"errorMessage":"Task {task_uuid} is in progress but has not registered an abort handler (is_abortable is not true)","messagePattern":"Task (.+?) is in progress but has not registered an abort handler \\(is_abortable is not true\\)","errorType":"exception","errorClass":"TaskNotAbortableError","httpStatus":400,"severity":"error","filePath":"superset/daos/tasks.py","lineNumber":240,"sourceCode":"        # Already aborting - idempotent success\n        if task.status == TaskStatus.ABORTING.value:\n            logger.info(\"Task %s is already aborting\", task_uuid)\n            return task\n\n        # Already finished - cannot abort\n        if task.status not in ABORTABLE_STATES:\n            return None\n\n        # PENDING: Go directly to ABORTED\n        if task.status == TaskStatus.PENDING.value:\n            task.set_status(TaskStatus.ABORTED)\n            logger.info(\"Aborted pending task: %s (scope: %s)\", task_uuid, task.scope)\n            return task\n\n        # IN_PROGRESS: Check if abortable\n        if task.status == TaskStatus.IN_PROGRESS.value:\n            if task.properties_dict.get(\"is_abortable\") is not True:\n                raise TaskNotAbortableError(\n                    f\"Task {task_uuid} is in progress but has not registered \"\n                    \"an abort handler (is_abortable is not true)\"\n                )\n\n            # Transition to ABORTING (not ABORTED yet)\n            task.set_status(TaskStatus.ABORTING)\n            db.session.merge(task)\n            logger.info(\"Set task %s to ABORTING (scope: %s)\", task_uuid, task.scope)\n\n            # NOTE: publish_abort is NOT called here - caller handles it after commit\n            # This prevents race conditions where listeners check DB before commit\n\n            return task\n\n        return None\n\n    # Subscription management methods\n","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/daos/tasks.py#L222-L258","documentation":"Raised by TaskDAO's abort path when a task is TaskStatus.IN_PROGRESS but its properties_dict does not have is_abortable set to True. Abortability is opt-in: the component that starts a long-running task must register an abort handler and set the is_abortable property; aborting an in-progress task without that registration cannot work, so TaskNotAbortableError (status 400) is raised. PENDING tasks are aborted directly and terminal states are filtered out beforehand, so this error is specific to running-but-unabortable tasks.","triggerScenarios":"Calling the task-abort API/DAO for an IN_PROGRESS task whose executor never called the equivalent of set_properties(is_abortable=True) with a registered handler; aborting tasks created by older code paths or external producers that predate the abort-handler protocol; racing an abort against a task that is between IN_PROGRESS and handler registration.","commonSituations":"Adopting the tasks framework for a new background job and skipping abort-handler registration; version skew where the UI shows an Abort button for task types that do not support it; third-party task producers writing rows with status IN_PROGRESS but no properties.","solutions":["In the task executor, register the abort handler and set properties {'is_abortable': True} (via update_properties) before/when transitioning the task to IN_PROGRESS.","Only expose Abort in the UI for task types known to register handlers; derive that from task.properties.is_abortable.","If aborting legacy tasks, first check properties_dict.get('is_abortable') is True and surface 'not abortable' rather than issuing the call.","Handle TaskNotAbortableError (400) as non-retryable — retrying will not register a handler."],"exampleFix":"# before\nTaskDAO.abort_task(task_uuid)  # IN_PROGRESS without is_abortable -> TaskNotAbortableError 400\n\n# after\n# executor side: register abortability before long work\ntask.update_properties({'is_abortable': True})\nregister_abort_handler(task_uuid, handler)\ntask.set_status(TaskStatus.IN_PROGRESS)\n\n# caller side: guard before aborting\nprops = TaskDAO.get_task(task_uuid).properties_dict\nif props.get('is_abortable') is True:\n    TaskDAO.abort_task(task_uuid)","handlingStrategy":"validation","validationCode":"task = TaskDAO.get_task(task_uuid)\nprops = task.properties_dict if task else {}\nif task and task.status == 'IN_PROGRESS' and props.get('is_abortable') is not True:\n    raise ValidationError('task is running but not abortable')  # block before calling abort","typeGuard":null,"tryCatchPattern":"from superset.commands.tasks.exceptions import TaskNotAbortableError\ntry:\n    TaskDAO.abort_task(task_uuid)\nexcept TaskNotAbortableError:\n    # 400 and non-retryable: surface 'cannot abort' and let the task finish/timeout\n    notify(f'task {task_uuid} cannot be aborted')","preventionTips":["Register abort handlers and set is_abortable=True before moving tasks to IN_PROGRESS.","Gate Abort buttons on properties_dict.is_abortable, not on task status alone.","Treat TaskNotAbortableError as non-retryable; fix the executor's registration instead."],"tags":["tasks","abort","dao","state-machine","validation"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}