Interrupting error event sub-process leaves a null delete reason on the activities it terminates
Version: 8.0.0, and unchanged on main as of 2026-08-12.
Database Vendor: PostgreSQL
SpringBoot: 4.1.0
Summary
When an interrupting error event sub-process catches a BpmnError, the executions it terminates are deleted with a null delete reason. ACT_HI_ACTINST.DELETE_REASON_ is therefore null on every activity it killed, making a terminated activity indistinguishable in history from one that completed normally.
Every other interrupting event sub-process start type records a reason.
Why this looks unintended
org.flowable.engine.history.DeleteReason declares EVENT_SUBPROCESS_INTERRUPTING = "event subprocess", and five sibling behaviors use it:
EventSubProcessTimerStartEventActivityBehavior
EventSubProcessMessageStartEventActivityBehavior
EventSubProcessSignalStartEventActivityBehavior
EventSubProcessEventRegistryStartEventActivityBehavior
EventSubProcessVariableListenerlStartEventActivityBehavior
each as DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(" + startEvent.getId() + ")".
BoundaryEventActivityBehavior likewise records DeleteReason.BOUNDARY_EVENT_INTERRUPTING.
The error path is the only one that does not. In ErrorPropagation.executeEventHandler:
if (parentExecution.isProcessInstanceType()) {
executionEntityManager.deleteChildExecutions(parentExecution, null, true);
ErrorPropagation never references DeleteReason at all.
This also contradicts the documented contract of the field. The javadoc for HistoricActivityInstance#getDeleteReason() reads:
Returns the delete reason for this activity, if any was set (if completed normally, no delete reason is set)
An activity terminated by this path satisfies that description in representation while being its opposite in fact.
Reproduction
This gist contains a test in the style of the existing DeleteReasonTest, which has testInterruptingBoundaryEvent but no event-sub-process equivalent. ErrorEventSubProcessDeleteReasonTest contains:
testInterruptingErrorEventSubProcess — fails. A forked process parks one branch on a user task; the other raises a BpmnError from a start execution listener. The interrupting error event sub-process terminates the scope, and the parked branch's activity ends with getDeleteReason() == null.
testInterruptingBoundaryEventForComparison — passes, showing the boundary-event path records its reason.
Impact
Any consumer building an audit or monitoring view from ACT_HI_ACTINST reports these activities as completed. In our case a user task that was destroyed by the interruption showed as "Complete" to an approver-facing audit surface, with no signal anywhere in history that it had been terminated.
ACTIVITY_CANCELLED is dispatched and is a viable hook, but it requires listening and persisting alongside history rather than reading history, which is a very different integration for what the delete-reason column already expresses everywhere else.
Suggested fix
Pass the reason the sibling behaviors pass. fix.patch is attached; both branches of executeEventHandler that delete executions currently pass null as the deleteReason argument:
executionEntityManager.deleteChildExecutions(parentExecution,
DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(" + event.getId() + ")", true);
...
executionEntityManager.deleteExecutionAndRelatedData(currentExecution,
DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(" + event.getId() + ")", false);
I have deliberately left cancelActivity alone to keep the change minimal, even though the sibling behaviors pass the interrupting start event there and this path passes null. Happy to include that too if you would prefer the paths to match exactly.
I could not find an existing report for this on the issue tracker or the forum, but I may well have missed one.
Interrupting error event sub-process leaves a null delete reason on the activities it terminates
Version: 8.0.0, and unchanged on
mainas of 2026-08-12.Database Vendor: PostgreSQL
SpringBoot: 4.1.0
Summary
When an interrupting error event sub-process catches a
BpmnError, the executions it terminates are deleted with anulldelete reason.ACT_HI_ACTINST.DELETE_REASON_is therefore null on every activity it killed, making a terminated activity indistinguishable in history from one that completed normally.Every other interrupting event sub-process start type records a reason.
Why this looks unintended
org.flowable.engine.history.DeleteReasondeclaresEVENT_SUBPROCESS_INTERRUPTING = "event subprocess", and five sibling behaviors use it:EventSubProcessTimerStartEventActivityBehaviorEventSubProcessMessageStartEventActivityBehaviorEventSubProcessSignalStartEventActivityBehaviorEventSubProcessEventRegistryStartEventActivityBehaviorEventSubProcessVariableListenerlStartEventActivityBehavioreach as
DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(" + startEvent.getId() + ")".BoundaryEventActivityBehaviorlikewise recordsDeleteReason.BOUNDARY_EVENT_INTERRUPTING.The error path is the only one that does not. In
ErrorPropagation.executeEventHandler:ErrorPropagationnever referencesDeleteReasonat all.This also contradicts the documented contract of the field. The javadoc for
HistoricActivityInstance#getDeleteReason()reads:An activity terminated by this path satisfies that description in representation while being its opposite in fact.
Reproduction
This gist contains a test in the style of the existing
DeleteReasonTest, which hastestInterruptingBoundaryEventbut no event-sub-process equivalent.ErrorEventSubProcessDeleteReasonTestcontains:testInterruptingErrorEventSubProcess— fails. A forked process parks one branch on a user task; the other raises aBpmnErrorfrom a start execution listener. The interrupting error event sub-process terminates the scope, and the parked branch's activity ends withgetDeleteReason() == null.testInterruptingBoundaryEventForComparison— passes, showing the boundary-event path records its reason.Impact
Any consumer building an audit or monitoring view from
ACT_HI_ACTINSTreports these activities as completed. In our case a user task that was destroyed by the interruption showed as "Complete" to an approver-facing audit surface, with no signal anywhere in history that it had been terminated.ACTIVITY_CANCELLEDis dispatched and is a viable hook, but it requires listening and persisting alongside history rather than reading history, which is a very different integration for what the delete-reason column already expresses everywhere else.Suggested fix
Pass the reason the sibling behaviors pass.
fix.patchis attached; both branches ofexecuteEventHandlerthat delete executions currently passnullas thedeleteReasonargument:I have deliberately left
cancelActivityalone to keep the change minimal, even though the sibling behaviors pass the interrupting start event there and this path passesnull. Happy to include that too if you would prefer the paths to match exactly.I could not find an existing report for this on the issue tracker or the forum, but I may well have missed one.