When a block fails, a flow has two ways to recover: wrap a section in a
handle-errors block for inline recovery, or give the whole flow a sibling
error: chain as a last resort. Both run a recovery chain that sees the failure
as vars.error. Anything you don't catch ends the message as failed.
handle-errors blockA composite with two slots: a process chain and an error chain. The process chain runs; if any block in it fails, the error chain runs instead, with vars.error set. If the error chain succeeds, the flow continues normally — the failure is recovered. The error chain sees the message as it entered the block.
- type: handle-errors
name: charge
process:
- type: rest
name: call-charge
settings:
connector: payments
method: POST
path: /charges
body: '{"amount": body.amount}'
error:
# runs only if the process chain failed
- type: set-payload
settings:
value: '{"status": "degraded", "reason": vars.error.message}'
error: chainA root flow may declare an error: chain as a sibling of process. If the process chain fails and nothing caught it, the message is handed to the error chain with vars.error set; its output becomes the flow's result. It's the difference between a per-section try/catch (handle-errors) and a whole-flow safety net.
flows:
- name: charge-flowlevel
source: { connector: api, type: http, settings: { path: /flowlevel } }
process:
- type: rest
name: call-charge
settings: { connector: payments, method: POST, path: /charges, body: '{"amount": body.amount}' }
error:
- type: set-variable
settings: { name: httpStatus, value: "502" }
- type: set-payload
settings:
value: >
{
"error": vars.error.message,
"failedBlock": vars.error.block,
"flow": vars.error.flow
}
vars.errorBoth recovery chains receive a structured error on the message:
| Field | Contains |
|---|---|
vars.error.message | The failing block's error string. |
vars.error.flow | The enclosing flow (or handle-errors block) name. |
vars.error.block | The failing block's label; empty if it didn't originate in a leaf block. |
For flows fronted by an http source, set vars.httpStatus (100–599) to control the response code. If you never set it, the status follows the flow outcome.
- type: set-variable
settings: { name: httpStatus, value: "502" }
Read just before the response is written; an out-of-range or unset value falls back to the outcome default.
| completed | 200 (or your vars.httpStatus) |
| dropped | 204 No Content |
| failed (uncaught) | 500 with {"error": …} |
| timed out | 504 Gateway Timeout |
handle-errors block nor a flow error: chain catches the failure, the message ends as failed — a terminal event on the flow-event bus — and an HTTP source returns 500.ai-retryFor failures an LLM can fix, ai-retry runs a protected process chain and, on error, lets the model inspect vars.error and the message, revise the body, and re-run the chain up to maxAttempts. When attempts are exhausted it falls through to the optional error chain. It pairs naturally with ai-mapping, which errors on schema-validation failure.
- type: ai-retry
name: resilient-charge
connector: claude
maxAttempts: 3
prompt: >
Inspect vars.error and the body, correct it (e.g. fix a malformed amount),
and produce a revised message to retry.
process:
- type: ai-mapping
settings: { connector: claude, prompt: "Build a charge request.", outputSchema: "…" }
error:
- type: set-payload
settings: { value: '{"status": "degraded", "reason": vars.error.message}' }