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.
A deployment has a maxRetries setting, and the number of attempts a step gets is
maxRetries + 1.
maxRetries | Attempts | Behaviour |
|---|---|---|
0 (the default) | 1 | A step that throws fails the execution immediately |
3 | 4 | Three 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.
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.
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.
Three controls end a run without it being a failure:
| Call | Result |
|---|---|
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.