Status: design placeholder / engine-level feature. Filed to capture the motivation, not scheduled.
Today
A node that returns { success: false } fails the entire run. AutomationEngine.execute catches it, writes the run-history row, and returns { success: false, error }. The only recovery available is flow-level errorHandling.strategy: 'retry', which re-runs the whole flow from the start — useless when the failure is deterministic (a bad filter, a validation rejection, a 4xx from an http node).
There is no way to say "if this node fails, take this other path."
Why it matters now
Guardrails that refuse to execute are the platform's main defense against silently-wrong data operations, and we keep (correctly) adding them:
Every one of them makes a node fail hard, and every one therefore aborts the run. That is the right default — a widened delete_record must not proceed — but it means the only available response to a guard is "the whole automation stops." The strictness debate on #3810 was really a debate about this: the objection was never "the guard is wrong," it was "aborting is expensive." An error branch decouples the two.
It also generalizes well past guards. A get_record that finds nothing, an http node that 404s, a connector_action that rate-limits — all are ordinary business conditions an author would like to route around, and today each one kills the run.
Sketch
An out-edge with an error semantic, resolved when the source node fails:
edges: [
{ source: 'fetch_account', target: 'use_account', label: 'ok' },
{ source: 'fetch_account', target: 'notify_admin', type: 'error' },
]
Open questions:
Related
Status: design placeholder / engine-level feature. Filed to capture the motivation, not scheduled.
Today
A node that returns
{ success: false }fails the entire run.AutomationEngine.executecatches it, writes the run-history row, and returns{ success: false, error }. The only recovery available is flow-levelerrorHandling.strategy: 'retry', which re-runs the whole flow from the start — useless when the failure is deterministic (a bad filter, a validation rejection, a 4xx from anhttpnode).There is no way to say "if this node fails, take this other path."
Why it matters now
Guardrails that refuse to execute are the platform's main defense against silently-wrong data operations, and we keep (correctly) adding them:
{…}before the query engine sees it #3810 — a CRUD node refuses when interpolation erased a filter conditionreadonlywrite semantics — keep user-context strip, add design-time lint for flow writes to readonly fields (#2948 follow-up) #3425 — areadonlyflow write is a certain no-opEvery one of them makes a node fail hard, and every one therefore aborts the run. That is the right default — a widened
delete_recordmust not proceed — but it means the only available response to a guard is "the whole automation stops." The strictness debate on #3810 was really a debate about this: the objection was never "the guard is wrong," it was "aborting is expensive." An error branch decouples the two.It also generalizes well past guards. A
get_recordthat finds nothing, anhttpnode that 404s, aconnector_actionthat rate-limits — all are ordinary business conditions an author would like to route around, and today each one kills the run.Sketch
An out-edge with an error semantic, resolved when the source node fails:
Open questions:
label: 'error'or a first-classtype: 'error'?type: 'back'(ADR-0044) is the existing precedent for an edge whose type changes traversal semantics, which argues fortype.{<nodeId>.error}) or the branch cannot act on it.{…}before the query engine sees it #3810 closed.errorHandling.retry— does a handled node still count toward flow-level retry? Presumably not: the failure was handled.type: 'back'treatment approvals use, orregisterFlowwill reject legitimate retry-ish shapes.successwith a warning on the step, or a distinct status? The Runs surface should not report a clean success with no trace of the failure (service-automation: runAs:'user' runs data ops with a credential-less user — trigger context never carries the actor's permission sets/positions (follow-up to #1888) #3356 / service-automation: update_record reportssuccesswhen written fields are silently stripped — no observability for dropped writes (split from #3356) #3407 lesson).Related
{…}before the query engine sees it #3810 — the guard whose cost motivated thistype: 'back', the existing precedent for edge-type-driven traversal