Promise.all over two forever-loops is a silent single point of failure

27 July 2026

I found this reading someone else's production code this afternoon. I've reported it to them privately and I'm not naming the project, because the pattern is the useful part and it is not theirs alone — I have written it myself.

The shape is a supervisor that runs two concurrent loops, each intended to run for the life of the process:

await Promise.all([
  watchdogLoopForever(...),   // drains a queue, keeps work alive
  handleJobsForever(...),     // pulls new work, schedules it
]);

It reads as "run both forever." It does something else.

Three properties that combine badly

1. Promise.all rejects on the first rejection and does not cancel the others. There is no cancellation in the promise model. The sibling keeps running, detached, with nothing awaiting it.

2. A for(;;) wrapper does not make a loop resilient. This looks like a retry and is not one:

for (;;) { await loop(); }   // a rejection exits the loop entirely

It restarts on return, never on throw. If the intent is "keep going," the try/catch is load-bearing and its absence is invisible.

3. The work-producing loop usually has more error handling than the work-guaranteeing loop, because it is the one people exercise. The guarantee is the part that only runs in failure, which is the part nobody tests.

What happens

One transient error on the guarantee side — a throttled API call, a credential refresh, a malformed message, a parse failure — takes that loop down permanently. The producer loop continues. It keeps accepting work, keeps scheduling it, keeps enqueueing to a queue with no consumer.

Work is not lost. It is duplicated: in-flight items lose whatever was holding their visibility or lease, become available again, and get picked up a second time while the first execution is still running.

And the process stays alive and busy the entire time. It is logging normal throughput. CPU looks right. Any liveness probe, any heartbeat, any "is it up" check passes.

The broken state and the healthy state produce the same observable artifact.

That is the whole reason this class is expensive. Nobody finds it by looking, because looking returns "running." It surfaces later as duplicate side effects in a reconciliation nobody connected to an incident three weeks earlier.

The fixes are small

Make the restart real. Put the try/catch inside the forever loop, so a poison message is a skipped message rather than a permanent stop. Log it and continue.

Make the failure loud instead of partial. Promise.all is the wrong combinator for "these should never finish." Use Promise.allSettled — or better, Promise.race — and treat any settlement as fatal: if a loop that should run forever has stopped, kill the process and let your supervisor restart the whole thing cleanly. A crashed container is a good outcome. A half-running one is not.

Assert the invariant you actually depend on. Not "is the process up" but "has the guarantee loop completed a cycle in the last N seconds." That is the check that can come out against you. Liveness cannot.

The general form

Any time two components are supposed to be co-alive and only one of them produces visible output, you have this. The visible one becomes your health signal by default, and it is exactly the one that does not need watching.

Ask which of your concurrent loops would be silent if it died — and then go and check whether anything at all would notice.