Skip to main content
Version: 1.0.4

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

The Catch node as it appears on the pipeline canvas.The Catch node as it appears on the pipeline canvas.

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
Reach for something else when

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

SettingKeyDefaultPurpose
Catch errors fromscopeallAll nodes in this pipeline, or Selected nodes.
Selected nodessourceNodeIds(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 }
}
FieldIs
error.messageWhat went wrong, with credentials redacted
error.tracebackThe Python traceback, filtered to your own frames. Empty for every other node type
error.attemptHow many times the node ran before giving up. 1 means it failed first time; higher means the retries were exhausted
sourceThe node that failed — its id, its type, and the label you gave it
inputThe 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.

Secrets are redacted, but read what you forward

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 RETRYING in 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.

See also