Severity: medium
Found in: 2026-07-22 code review
Location
lib/dag/effects/dispatcher.rb:224 (worker_errors.compact.first) interacting with tick at :167-170.
Failure mode
When two workers fail in the same tick, the "winning" exception is chosen by worker index, not by severity or chronology. If worker 0 captures a StandardError (storage failure) and worker 1 captures a non-StandardError (Interrupt, SystemExit, NoMemoryError), .first yields the StandardError, so tick raises DispatchAbortedError — itself a StandardError — and the fatal exception is dropped entirely (it is not even set as #cause). The documented contract "Non-StandardError exceptions propagate unwrapped" is violated, and an upstream rescue => e will swallow what should have been a process-fatal signal.
Minimal trigger (reproduced)
parallelism: 2 against a thread_safe_for_dispatch? adapter; record 0's complete_effect_succeeded raises RuntimeError; record 1's handler raises Interrupt after a small sleep. tick raises DispatchAbortedError wrapping the RuntimeError; the Interrupt vanishes.
Fix direction
When selecting among captured worker errors, prefer non-StandardError (propagate it unwrapped); optionally also prefer chronological first-failure among same-severity errors (see companion issue on #cause attribution).
Severity: medium
Found in: 2026-07-22 code review
Location
lib/dag/effects/dispatcher.rb:224(worker_errors.compact.first) interacting withtickat:167-170.Failure mode
When two workers fail in the same tick, the "winning" exception is chosen by worker index, not by severity or chronology. If worker 0 captures a
StandardError(storage failure) and worker 1 captures a non-StandardError(Interrupt,SystemExit,NoMemoryError),.firstyields theStandardError, sotickraisesDispatchAbortedError— itself aStandardError— and the fatal exception is dropped entirely (it is not even set as#cause). The documented contract "Non-StandardError exceptions propagate unwrapped" is violated, and an upstreamrescue => ewill swallow what should have been a process-fatal signal.Minimal trigger (reproduced)
parallelism: 2against athread_safe_for_dispatch?adapter; record 0'scomplete_effect_succeededraisesRuntimeError; record 1's handler raisesInterruptafter a small sleep.tickraisesDispatchAbortedErrorwrapping theRuntimeError; theInterruptvanishes.Fix direction
When selecting among captured worker errors, prefer non-
StandardError(propagate it unwrapped); optionally also prefer chronological first-failure among same-severity errors (see companion issue on#causeattribution).