Skip to content

Errors and retries

Attempt fails flow.do step Next attempt default: 5 in total Your workflow await throws Instance ERRORED can retry no retry not caught
Attempt fails flow.do step Next attempt default: 5 in total Your workflow await throws Instance ERRORED can retry no retry not caught
A failed attempt runs again while attempts are left and the error allows it; when attempts run out or the error stops retries, the error reaches your workflow function, and only an uncaught error ends the instance.

This page shows what IdentityFlow does when a step fails and how to change it. Retries are on by default: every flow.do step makes up to five attempts, one minute apart. After the last one, or after an error that stops retries, your workflow decides whether the instance ends.

First step: check whether the default fits each step that calls another system. Then go where your case is:

Every flow.do step is retried by default: up to five attempts in total, one minute apart. Change this with the step’s retries option:

retries valueMeaning
falseRun once; no retry.
a number, such as 3The maximum number of attempts, including the first. 3 means at most three runs. 0 and 1 both mean a single run.
a duration string, such as '10 seconds'The delay between attempts. The number of attempts and the backoff keep their defaults.
{ limit, delay, backoff }All three settings. Each field is optional. limit: false retries without limit.

backoff decides how the delay grows:

  • 'constant' (the default) waits delay before every attempt.
  • 'linear' waits delay times the number of attempts made so far.
  • 'exponential' waits delay times two to the power of the attempts made so far. A delay of two seconds waits four seconds after the first failed attempt and eight after the second.

Linear and exponential delays are capped at one day.

Write durations as strings, such as '2 seconds' or '1 minute'. A number in delay is read as milliseconds; 0 fails in 0.3.0 and errors the instance.

This step from Retry a step and stop on a final error makes at most three attempts, waits four and then eight seconds between them, and stops at once on an error that another attempt cannot fix:

return await flow.do(
'book vacation days',
{ retries: { limit: 3, delay: '2 seconds', backoff: 'exponential' } },
async () => {
try {
return await payroll.book(flow.params.employeeId, flow.params.days);
} catch (error) {
if (isInsufficientBalance(error)) {
throw new NonRetryableError('Vacation balance is exhausted', { cause: error });
}
throw error;
}
},
);
{ retries: { limit: 3, delay: '2 seconds', backoff: 'exponential' } },
  1. three attempts in total
  2. base delay
  3. waits 4 s, then 8 s

A step also accepts a timeout option. 0.3.0 records it with the step but does not enforce it: an attempt runs until your callback returns or throws. To bound a call, give your client its own timeout, for example by passing AbortSignal.any([signal, AbortSignal.timeout(30_000)]), where signal is the step’s signal.

Retry defaults for all steps can also be set for a workflow or for the engine; see Retry and error contracts.

Two conditions make a retry safe. The step must have a stable name, so IdentityFlow recognizes the second attempt as the same step. And everything the step calls must tolerate receiving the same request again; see External effects.

Some errors end the step without another attempt:

  • NonRetryableError says that another attempt cannot succeed. Throw it for a permanent refusal, as the example does. Pass the original error as cause to keep it.
  • ValidationError and LockedError are not retried unless you create them with retryable: true. Bad input usually stays bad.
  • TimeoutError, AssertionError, AuthorizationError, NotFoundError, ForbiddenError and ConflictError are not retried either.

Any WorkflowError created with retryable: false also stops retries. Any other error, including a plain Error and a WorkflowError without retryable: false, is retried according to retries. All error classes are exported from @identity-flow/sdk. The full list is under Errors you throw or handle.

When the last attempt fails, or an error stops retries, the step ends as ERRORED and await flow.do(…) throws that error in your workflow function.

  • If your workflow does not catch the error, the instance ends as ERRORED. Steps of that instance that were still running or waiting end as ERRORED as well.
  • If your workflow catches the error, it can continue, for example with a different step or a result that records the failure. Rethrow errors for which isAbortError(error) is true; see Cancellation boundary.

Between two attempts, the failed attempt is recorded as a STEP_ERRORED event with the time of the next attempt, and the instance stays ACTIVE.

YOUR WORKFLOW FUNCTION Attempt runs callback Retry? limit, retryable Waits for the delay COMPLETED step ERRORED step, final await flow.do throws Continues error caught Instance ERRORED throws: STEP_ERRORED yes next attempt returns, no schema no rejects catches escapes
YOUR WORKFLOW FUNCTION Attempt runs callback Retry? limit, retryable Waits for the delay COMPLETED step ERRORED step, final await flow.do throws Continues error caught Instance ERRORED throws: STEP_ERRORED yes next attempt returns, no schema no rejects catches escapes
Every failed attempt is recorded; it is retried while the limit allows it and the error is retryable; after that, the error reaches your workflow function, and only an uncaught error ends the instance.

Retry a step and stop on a final error shows both outcomes, a retry that succeeds and a refusal that ends the instance, with a test.

When a step calls another system, IdentityFlow delivers that call at least once. It can arrive twice. A process can stop after the other system accepted the request but before IdentityFlow recorded the step’s result; the next attempt then sends the request again. No retry setting prevents this, and neither does cancellation.

IDENTITYFLOW RECEIVING SYSTEM Attempt 1 key = step.id Process stops result not saved Attempt 2 same step.id Result recorded e.g. booking ID Change applied key kept Already done key seen before sends request reply lost retries same request confirmation
IDENTITYFLOW RECEIVING SYSTEM Attempt 1 key = step.id Process stops result not saved Attempt 2 same step.id Result recorded e.g. booking ID Change applied key kept Already done key seen before sends request reply lost retries same request confirmation
A request can arrive twice; with the step's ID as idempotency key, the receiving system answers the repeat as already done.

So every system that a step changes needs to recognize a repeated request:

  • Send an idempotency key that the receiving system understands, and let it treat a second request with the same key as already done. A stable key is the step’s ID: the step callback receives the step as step, and step.id stays the same for every attempt of that step.
  • Or make the change itself repeatable, such as “set the role to approver” instead of “add a role”.

IdentityFlow records whatever your step callback returns. Return the receiving system’s confirmation, such as its booking ID, so the record shows that the other side accepted the work and not only that a request was sent.

An ERRORED instance is finished. 0.3.0 has no command that resumes or retries it. Find the cause, fix it, and start a new instance.

To find the cause:

  • An account with the viewer access function sees the instance’s business steps and their status on the instance page in the IdentityFlow UI. An instance that failed with an Error, such as most ERRORED instances, shows no business data there; an operator reads its error message in the Operations view. A dialog answer that fails its schema is refused when it is submitted. A recorded answer can still error an instance when the workflow continues: if it was recorded without that check, by 0.3.0 or because the check could not decide, or if the validation rules changed after it passed.
  • An account with the operator access function works with instances in the Operations view. While a step is still waiting for its next attempt, the instance is ACTIVE, and an operator can pause or terminate it. Operate running workflows describes the views and actions.
  • Reproduce the failure in a Workflow Test. flow.observe(instance) returns the instance, its steps and its events. Each failed attempt is a STEP_ERRORED event whose message reads Attempt #<n> errored for step "<name>" with "<error message>".

Do not repair a run by editing or deleting events. Events are the audit trail.

When you share an error report, name the workflow, the version, the step, who was acting and the error code, and leave out parameters, answers and credentials. The diagnostic of a Workflow Test has exactly this shape. For how an instance continues after a restart of the host, see Replay.