Catch New in 1.0.4
A single node that receives every failure in its pipeline, so you do not have to wire an error path out of each node individually. It has no input — the runtime dispatches it after a node fails — and one output, which you wire to whatever should handle the error.
Type: catch · Category: Output · Ports: no input · one output


When to use it
- Telling somebody a pipeline broke — a Telegram message, an email, a page
- Writing failures to a dead-letter table with the message that caused them
- Central error handling: one Catch per pipeline, each feeding a Link egress into one "Error Handling" pipeline
- Watching a few risky nodes specifically — a flaky external API, a database that goes away at night
To continue the flow with a substitute value, enable that node’s error output instead. A Catch starts a new branch and cannot resume the one that broke, so recovering in-flow through a Catch would mean duplicating the rest of the pipeline underneath it.
Settings
| Setting | Key | Default | Purpose |
|---|---|---|---|
| Catch errors from | scope | all | All nodes in this pipeline, or Selected nodes. |
| Selected nodes | sourceNodeIds | (empty) | Which nodes to watch, when the scope is Selected. |
The picker lists the nodes of the pipeline you are editing and nothing else. A selected node that has since been deleted is shown as missing rather than silently dropped — a quiet drop means a node stops being monitored and nobody finds out until a failure goes unreported.
What it emits
The output is an envelope, not an error string:
{
"error": {
"message": "dial tcp 10.0.0.7:5432: connect: connection refused",
"traceback": "",
"attempt": 3,
"timestamp": "2026-09-08T07:41:22Z"
},
"source": { "id": "n8", "type": "database-sql", "name": "Write downtime row" },
"input": { "line": "L3", "minutes": 12 }
}
| Field | Is |
|---|---|
error.message | What went wrong, with credentials redacted |
error.traceback | The Python traceback, filtered to your own frames. Empty for every other node type |
error.attempt | How many times the node ran before giving up. 1 means it failed first time; higher means the retries were exhausted |
source | The node that failed — its id, its type, and the label you gave it |
input | The payload that caused the failure, so the message can be retried, replayed or parked rather than only reported |
The message also keeps the failed node's metadata and origin, so an error handler can still answer an HTTP caller through an HTTP Response node.
A raw driver error routinely carries a connection string. The envelope is redacted where it is built, before any handler sees it — but an error handler's whole purpose is to send something somewhere else, so treat the destination as you would any other place plant detail lands.
What does not fire a Catch
- A retried attempt. Only the final, exhausted failure reports. Intermediate attempts show as
RETRYINGin the log. - A skipped node. One root failure would otherwise produce an error per downstream node, turning a single fault into a storm.
- A failure inside a Catch's own handler chain. A Catch never watches itself or anything downstream of it, even at scope All — that is what stops an error handler that fails from calling itself forever.
- A failure routed out of a node's error output. You wired that failure somewhere, so it goes there and only there.
Example — a dead-letter table
Catch (all nodes) → Python (shape a row) → SQL (INSERT INTO pipeline_failures)
The Python node has everything it needs in one message:
e = payload["error"]
s = payload["source"]
return {
"node": s["name"],
"kind": s["type"],
"message": e["message"],
"attempts": e["attempt"],
"at": e["timestamp"],
"body": payload["input"], # so it can be replayed
}
Example — one place for every pipeline's errors
A Catch is scoped to its own pipeline and cannot be widened. Compose instead of widening:
Pipeline A: Catch (all) → Link (egress "errors")
Pipeline B: Catch (all) → Link (egress "errors")
Error Handling pipeline: Link (ingress "errors") → Switch (by source.type) → …
That keeps ownership clear: editing pipeline A cannot start firing alerts wired into pipeline B, and deleting B cannot silently un-monitor A.
Gotchas
- Sending it to a REST API node? Set that node's Configuration Mode to Dynamic, or it sends its own static body and the error never leaves the machine.
- Scope is this pipeline, always. All nodes means every node in this pipeline. There is no cross-pipeline scope; use the Link pattern above.
- A run with nothing to report does nothing. A Catch has no input, so the scheduler meets it on every pass — it stays quiet, and writes no row to the execution log, unless there is a failure to report.
- At most four handler runs at a time. One dead database fails every node that touches it; the bound turns that storm into a queue.