WorkflowInstancesDeleting does not purge bookmark queue / dead-letter items
Problem
WorkflowInstancesDeleting already cascades bookmarks + activity/workflow execution logs, but bookmark queue and bookmark queue dead-letter rows keyed by WorkflowInstanceId are left behind.
Both stores already expose filter-scoped DeleteAsync (and filters already support WorkflowInstanceId), but nothing wires them to the instance-delete notification — same shape as Labels version-delete orphans (#8190).
Evidence (main / elsa-core)
What does cascade today
src/modules/Elsa.Workflows.Runtime/Handlers/
DeleteBookmarks→INotificationHandler<WorkflowInstancesDeleting>DeleteActivityExecutionLogRecords→ sameDeleteWorkflowExecutionLogRecords→ same
Published from WorkflowInstanceManager before store.DeleteAsync.
What does not
No handler under Handlers/ (or elsewhere) deletes:
BookmarkQueueItemviaIBookmarkQueueStore.DeleteAsync(BookmarkQueueFilter)BookmarkQueueDeadLetterItemviaIBookmarkQueueDeadLetterStore.DeleteAsync(BookmarkQueueDeadLetterFilter)
on WorkflowInstancesDeleting.
Store delete APIs exist (used elsewhere, not on instance delete)
IBookmarkQueueStore.DeleteAsync— called fromBookmarkQueueProcessor(per-item after success/dead-letter) andDefaultBookmarkQueuePurger(age /CreatedAtLessThan)IBookmarkQueueDeadLetterStore.DeleteAsync— API delete endpoint + age purger
Filters already key by instance
BookmarkQueueFilter.WorkflowInstanceId(…/Filters/BookmarkQueueFilter.cs)BookmarkQueueDeadLetterFilter.WorkflowInstanceId(…/Filters/BookmarkQueueDeadLetterFilter.cs)
Entities carry the FK: BookmarkQueueItem.WorkflowInstanceId, BookmarkQueueDeadLetterItem.WorkflowInstanceId.
Runtime symptom (not just a leak)
BookmarkQueueProcessor.ProcessItemAsync: when resume returns zero matching bookmarks (instance already gone + bookmarks already deleted by DeleteBookmarks), the item is retained (return true). Orphans sit until age purge / max delivery attempts → dead-letter noise, wasted worker cycles, and operator dead-letter lists that reference deleted instances.
Why it matters
- Persistence hygiene: instance delete is incomplete relative to sibling runtime stores.
- Correctness: queue worker keeps probing deleted instances; dead-letter accumulates unreplayable junk.
- Subtractive: wire existing
DeleteAsync+ filters — no new store surface required for the common path.
Proposed subtractive fix
- Add a
DeleteBookmarkQueueItems(name flexible) handler onWorkflowInstancesDeletingthat, for each id innotification.Ids, callsIBookmarkQueueStore.DeleteAsync(new BookmarkQueueFilter { WorkflowInstanceId = id })and the same for dead-letter viaBookmarkQueueDeadLetterFilter. - Optional small contract polish: add
WorkflowInstanceIdsto both filters (mirrorBookmarkFilter.WorkflowInstanceIds/ActivityExecutionRecordFilter.WorkflowInstanceIds) so bulk instance delete is one round-trip — not required for correctness. - Do not rely on age purger as the cascade; purger is TTL hygiene, not delete semantics.
Related / not duplicate
- #8190 — Labels version-delete orphans (definition/version cascade; same pattern, different notification)
- Age-based
DefaultBookmarkQueuePurger/ dead-letter APIs — complementary, not a substitute - Avoid: UserTasks / Alterations instance projections (tracked elsewhere)
Light milestone note for Crew Lead: Soft Backlog / correctness; small handler wiring; off 3.9 unless queue noise is user-blocking. Do not auto-assign.
Source: elsa-workflows/elsa-core