TaskSultan Docs tasksultan.com

Building automations

Exceptions and retries

The exception classes the framework names, the retry policy that bounds system failures and the difference between a business and a system failure.

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, with code and kind: 'business'.
  • SystemException, with code, kind: 'system' and a retryable flag. retryable defaults to true; a SystemException built with retryable: false is 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 BusinessException stays business even when it is thrown from inside a Playwright callback. Identity is decided by the error's shape, not only by instanceof, so a bundled package classifies correctly across module boundaries.
  • An unhandled native browser dialog becomes a BusinessException. With no dialogs policy the runner records the dialog and fails the transaction rather than let the automation continue on an answer the application never gave.
  • If an onEnd hook throws, the run status becomes failed even when every transaction succeeded.