Appearance
Error handling
Any node that talks to the outside world can fail — an HTTP call times out, an email bounces, a database query is rejected. FlowRunner gives every node a built-in try/catch so you decide, per node, whether a failure is handled or fatal.
The error port
Every working node has a hidden error output port. When the node fails, the engine looks at that port:
- If you wired the error port to another node, the failure routes down that branch. The run keeps going — the error is caught. This is your "catch" block.
- If you left the error port unwired, the failure surfaces: the node continues on its normal default path, and the run is marked failed overall.
Wiring the error port is how you turn an unavoidable "this might fail" into a graceful branch — retry, notify, fall back to a default, or return a friendly message.
TIP
Only wire the error port where a failure is genuinely recoverable. For everything else, leaving it unwired is correct — you want the run to fail loudly so it shows up as an error in Run history.
refs.error
Whenever a node fails, the engine writes a bucket named error into the flow's shared refs:
error = {
message: "http: fetch failed", // what went wrong
nodeId: "node_a1b2c3" // which node produced it
}Your handler branch can read ${error.message} in an email or a Template to report exactly what happened, and error.nodeId to say where. Because it lives in refs, the error is available to every node downstream of the failure.
When a run is reported as failed
A run is reported as failed (HTTP 500, ok: false) only when a node failed and the flow never reached an End node. In other words:
- A caught failure that routes to a handler and still reaches an End → the run succeeds and returns your handler's response.
- An uncaught failure that ends the path without reaching End → the run fails, and the response names the failing node.
The failed response includes the error message and the id of the node that failed, so callers and Run history both point at the same place.
Guardrails
Two hard limits protect the server from a flow that would otherwise never stop — an accidental cycle in the wiring, or a loop over an enormous list:
| Guardrail | Limit | What happens when hit |
|---|---|---|
| Execution steps | 10,000 | The run aborts with "execution step limit exceeded (possible cycle)". |
| ForEach iterations | 10,000 | The ForEach node fails with a "too many iterations" error. |
The step cap counts every hand-off from one node to the next across the whole run, so a tight loop wired back on itself is stopped rather than hanging the request. The iteration cap is checked before a ForEach starts, so an oversized collection fails fast instead of grinding through millions of passes.
NOTE
These caps are safety nets, not design targets. A well-built flow finishes in a handful of steps. If you're anywhere near 10,000, revisit the wiring — you very likely have a cycle.