A transaction fails in one of two ways, so the framework treats them differently. A business failure is the data breaking a rule, so the unit is done and the run moves on. A system failure is infrastructure misbehaving, so the framework retries. This page covers both and the policy that bounds the second.
#The two failure lanes
BusinessException is an expected, domain-level failure: an invoice number in the wrong format, a required field the
application rejected. It is never retried. The transaction is marked failed and the loop continues.
SystemException is an infrastructure or flaky failure: a queue request that timed out, a service that returned a
500, an element that did not appear in time. It is retried under the policy below. Only when the attempts are
exhausted is the transaction marked failed.
throw new BusinessException('Invoice number "123" is not in INV-XXXX format', 'INVALID_INVOICE_NUMBER');
throw new SystemException('Order service returned HTTP 500', { code: 'ORDER_SERVICE_500' });
Both classes carry a code, a stable machine-readable string such as PO_NUMBER_REQUIRED. They also carry a kind of
business or system.
#The exception types the framework names
The taxonomy lives in framework/errors.ts.
BusinessException, withcodeandkind: 'business'.SystemException, withcode,kind: 'system'and aretryableflag.retryabledefaults totrue; aSystemExceptionbuilt withretryable: falseis a refusal the policy will not retry.AbortRequested, raised internally when an operator asks a run to stop. It is not an error and it unwinds the loop gracefully.
An error that is neither is wrapped in a SystemException with the code UNEXPECTED_ERROR, so a coding bug is retried
under the same bound rather than looping forever. The Orchestrator keeps its own pair for queue records,
TransactionBusinessException and TransactionSystemException, with the same two meanings.
The names RetryableException, ConfigurationException and RefusedException appear in the platform's vocabulary but
no source file defines or raises them yet, so this page leaves them undefined rather than inventing a behaviour for
them.
#The retry policy
The policy is RetryPolicyOptions, with defaults applied by normalizeRetryPolicy.
| Option | Default | Meaning |
|---|---|---|
maxAttempts |
3 | Total attempts including the first |
baseDelayMs |
800 | Delay before the first retry |
maxDelayMs |
8000 | Ceiling on any single delay |
backoffFactor |
2 | Multiplier applied per attempt |
The delay before attempt N is min(maxDelayMs, baseDelayMs * backoffFactor ** (N - 1)), so the defaults wait 800 ms
then 1600 ms. Only system failures are retried. A BusinessException propagates immediately. A SystemException
with retryable: false is rethrown at once. Between attempts the policy asks whether an operator requested a stop, so
a stop wins over one more try.
The policy is set per automation on the retry field of the definition and can be overridden for a single run.
#A named refusal is a non-retryable system failure
The framework uses SystemException with retryable: false for its own refusals, where retrying could not help. A
credential requested outside the Robot runtime is CREDENTIALS_UNAVAILABLE. A facade with no declared capability is
FILES_NOT_DECLARED or DESKTOP_NOT_DECLARED. A facade whose engine was not supplied is the matching
*_UNAVAILABLE. The same shape covers a missing input workbook (EXCEL_INPUT_MISSING) and a request to stop
(ABORT_REQUESTED).
#Things that catch people out
- A
BusinessExceptionstays business even when it is thrown from inside a Playwright callback. Identity is decided by the error's shape, not only byinstanceof, so a bundled package classifies correctly across module boundaries. - An unhandled native browser dialog becomes a
BusinessException. With nodialogspolicy the runner records the dialog and fails the transaction rather than let the automation continue on an answer the application never gave. - If an
onEndhook throws, the run status becomesfailedeven when every transaction succeeded.