Relationships
#2543 After a cancel or --timeout, a never-started dependency stays pending, so failed- and completed-gated cleanup steps are skipped
Opened by hammz · 9/25/2026· Shipped 9/28/2026
Summary
When a workflow run's signal aborts part-way through a level (cancel or --timeout), runJob() in src/domain/workflows/execution_service.ts enters cleanup mode for the next level and marks every step still running as failed (cancelled). Steps and forEach iterations in the aborted level that mergeWithConcurrency never started stay pending.
A cleanup step gated on the dependency with condition failed or completed then sees no terminal status and is skipped:
- a plain dependency that never started reports pending from JobRun.getStatus();
- a forEach dependency with any never-started iteration aggregates to running.
Neither satisfies failed or completed, so the cleanup never runs, although the run was aborted. This is most likely with a concurrency-limited forEach dependency, where later iterations are still queued when the signal fires.
Steps to reproduce
A job with a forEach step build over several items with concurrency 1, each iteration sleeping, and a step rollback with dependsOn build, condition type failed. Run with a --timeout that expires during the first build iteration. The first iteration is marked failed (cancelled), the rest stay pending, and rollback is skipped (dependency).
Expected
A step or iteration that never started because the run was aborted ends in a terminal state (for example failed or skipped as cancelled) before cleanup-mode levels evaluate their conditions, so failed- and completed-gated cleanup runs as swamp-club#1785 intended.
Context
Pre-existing for plain cleanup steps. Found by the verification adversarial review of swamp-club#2537: once that fix lands, forEach cleanup steps evaluate their dependsOn too, so they are affected in the same way (previously they ran because their condition was ignored).
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.