Storing incomplete executions breaks the execution order

:bullseye: What is your goal?

The goal is simple: with Store incomplete executions and Process data in order both enabled, execution order should hold even when a module fails and the run is later resumed. What ran before the failure stays before it. What was still pending stays after — including bundles still queued from a module upstream of the failure.

  • Router — the 1st route must finish before the 2nd starts.
  • Iterator — if it emits three bundles and the 2nd fails, the 3rd must wait until the 2nd is resolved.

This isn’t an exotic requirement. Make’s own Converger workaround is built on it: an extra filter-free route holds the common sequence, earlier routes store their output with Set variable, and the common route reads it back. It only works if the earlier routes finish first.

The Iterator case is just as ordinary. A webhook delivers an order with three line items, an Iterator splits them, and each is written to an invoicing system that assigns sequential line numbers. The lines only come out right if the items are written in the order they arrived.

:thinking: What is the problem & what have you tried?

Router. When a module in the 1st route fails and Store incomplete executions is enabled, the run does not stop. The 2nd route executes immediately and consumes its operation, while the rest of the 1st route is deferred into the incomplete execution and runs only on retry — manually or automatically — by which point the 2nd route has already committed. In the Converger case, the common route reads a variable the failed route never got to set.

Iterator. The 2nd bundle fails and is stored. The run continues and the 3rd bundle is processed straight away, leaving the 2nd to be finished later on retry. Bundles emitted in order are processed out of order. In the invoice example, item 3 takes line 2’s slot and item 2 lands at the end — the invoice no longer matches the order it came from.

In both cases a single run partially succeeds, in an order the scenario could never produce on a clean run. The reordering itself is never reported — the run ends with a warning about the original error, and the incomplete execution eventually reports as resolved.

The docs describe this, but not its consequence

  • Overview of error handling — with Store incomplete executions on, a bundle erroring inside one route doesn’t stop the scenario. The error becomes a warning, the run continues, the bundle stops at the failing module in that route but is still pushed through all other matching routes.
  • Manage incomplete executions — the resume is a fragment, not a replay. Retry starts “with the module that caused the error” using the original input.

However, neither mentions that the run might be reordered.

Same symptom reported in Jan 2025, never answered:

What should change

The stored incomplete execution should carry the entire remainder of the run — the failed module’s downstream path and the output still pending from upstream modules — rather than just the downstream fragment. A failure should halt the run and store it in full, rather than continuing to process pending output from an upstream Router, Iterator, or any other module. Resuming should then process everything that was left, in the order it would have run.

And this isn’t limited to the two setups above. Router and Iterator are only the most obvious cases. In fact, the problem arises whenever any module preceding the failed one still has unprocessed bundles — an Iterator, a Search or List module returning multiple results, or a Router pushing the same bundle through several routes. In all of these the scenario execution continues after the failure.

Some context on why this matters beyond the mechanics.

The most annoying thing about running automations is having to keep paying attention to them. That defeats the point: they should run unattended, and when something goes wrong, recover on their own.

Most errors aren’t caused by Make — they come from the services it integrates. A scenario doing thousands of operations against cloud APIs over an unreliable network will hit errors, even when every service involved is up 99% of the time. That’s normal and expected, and incomplete executions are the right, well-proven answer to it. But only if what’s stored is the whole run.

The practical effect of storing a fragment instead of the full run is that automatic retry can’t be trusted. Every incomplete execution becomes something a human has to open and reason about — did anything downstream already commit, is the state now inconsistent, is it safe to resume? That’s exactly the attention the automation was supposed to remove.

To pre-empt the obvious question: there is no error handler attached to the failing module in either scenario above. This is a bare unhandled error with Store incomplete executions enabled. And Retry (formerly Break) doesn’t help — it again stores the execution fragment in Incomplete executions and continues the run.

To be clear, there’s nothing wrong with incomplete executions as a concept. Capturing a failed unit of work for later retry is a standard, well-proven pattern — a dead letter queue by another name, and the right answer to transient failures in an integration platform.

The problem isn’t the pattern, it’s what gets stored. A DLQ holds the whole message; retrying it replays the complete unit of work. Make stores a fragment — the failed module and its downstream path — while the rest of the run has already moved on. That’s what breaks, and it’s the only part that needs to change.

So I don’t think this needs the whole error model revisited. Store the full remaining run rather than a fragment, and hold the pending output of upstream modules with it. The retry semantics, the UI, the automatic retry — all of that can stay exactly as it is.

I think the important distinction here is that an incomplete execution is being treated more like a resumable error fragment than a snapshot of the whole pending run.

That explains why the behavior is especially problematic with an Iterator: bundle 2 can fail, bundle 3 can still commit, and the retry later resumes bundle 2. At that point Make isn’t really preserving the original execution order anymore.

For workflows where ordering matters, I’d expect the failure to effectively pause the pending sequence and resume from that point, rather than allowing later queued bundles or routes to commit first. The same applies to the Router + Converger pattern, since the downstream route may execute before the variable from the failed branch exists.