Retries and failure

When a step throws, the platform retries that step, not the whole execution. Steps that already succeeded keep their recorded results and are not run again.

How many attempts

A deployment has a maxRetries setting, and the number of attempts a step gets is maxRetries + 1.

maxRetriesAttemptsBehaviour
0 (the default)1A step that throws fails the execution immediately
34Three retries after the first attempt

It is a property of the deployment, not of the flow's code, so the same flow can be forgiving in one deployment and fail-fast in another. Starting a run by hand can override it for that run alone.

Retries are immediate

There is no backoff. An automatic retry re-enters the same step as soon as the failed attempt finishes, in the same worker.

That is the right behaviour for a transient glitch and the wrong one for a rate-limited API, which is why the platform gives you the timing controls directly rather than guessing:

async callTheApi() {
  const res = await fetch(url);
  if (res.status === 429) {
    this.rerun(30_000); // run this step again, 30s from now
    return;
  }
  return await res.json();
}

this.rerun(ms) re-runs the current step after the delay you name. The execution is re-queued rather than held open in the worker, so a long wait costs nothing while it waits. Called without a delay, it re-runs immediately.

this.delay(ms) is its sibling for the next step: it lets the current step finish and succeed, then waits before continuing.

When the attempts run out

The step is recorded FAILED, the execution is recorded FAILED, and no later step runs. The failing step's error is stored on the execution, so the execution page shows which step failed and why.

If the class declares onFailure, it is invoked at that point:

onFailure(step: string, error: unknown) {
  this.log("ERROR", `giving up after ${step}`, { error: String(error) });
}

onFailure is a hook, not a step. It does not get its own retries and it cannot rescue the execution — the run has already failed by the time it is called.

Ending a run on purpose

Three controls end a run without it being a failure:

CallResult
this.exit(result?)Skips the remaining steps; the execution is SUCCEEDED
this.cancel(result?)Skips the remaining steps; the execution is CANCELLED
this.pause()Suspends the run until something calls the resume URL it returns

exit also stops a retry: a step that calls it is not retried even if it errors afterwards.

You cannot combine them. Calling a second one after pause, cancel or exit throws.

What is not retried by this setting

Next