You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(cli,automation): catch label: 'error' written where type: 'fault' was meant (#3863) (#3893)
Two of the three items left open on #3863. Both make the fault-edge contract
legible; neither changes routing behaviour.
New lint `flow-error-label-not-fault`. `type: 'fault'` is what routes a failure;
`label` is cosmetic on an ordinary edge. So an edge written
{ source: 'charge_card', target: 'flag_for_review', label: 'error' }
is an ordinary out-edge, and traverseNext runs every unconditional out-edge in
parallel — the handler fires on every SUCCESSFUL run of the source, alongside
the real success path, and never on a failure. The run still aborts when the
node fails.
Silent both ways: the author believes failures are handled, and never notices
the handler running when nothing went wrong. Especially natural for an AI
author, since the label is exactly what the intent sounds like.
Narrow by construction, because a label IS load-bearing on a branching node — a
decision/approval executor returns a branchLabel and traversal prefers the edge
carrying it. Excluded: edges out of those node types, conditional edges, and
edges already typed 'fault'. Matches the obvious synonyms case-insensitively.
No findings against the shipped showcase.
An alias (accepting label:'error' as type:'fault') was considered and rejected:
two spellings for one concept read worse than one spelling plus a diagnostic
that names the fix.
Also pinned: a handled failure does not consume a flow-level retry. A fault edge
handles one node; errorHandling.retry replays the flow FROM THE START, re-running
everything that already succeeded. The two must not compound. That held by
construction — a routed failure never propagates out of executeNode — and is now
a test so a refactor of the catch path cannot quietly change it.
Docs and the automation skill gain both points, plus a note that `label` does
not select a path except on a branching node.
Copy file name to clipboardExpand all lines: content/docs/automation/flows.mdx
+26-1Lines changed: 26 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -564,7 +564,7 @@ Edges connect nodes and define the execution path:
564
564
|`target`|`string`| ✅ | Target node ID |
565
565
|`type`|`enum`| optional |`'default'` (success), `'fault'` (error), `'conditional'` (expression-guarded), or `'back'` (declared back-edge, ADR-0044); defaults to `'default'`|
566
566
|`condition`|`string`| optional | Boolean expression for branching |
567
-
|`label`|`string`| optional | Label displayed on the connector |
567
+
|`label`|`string`| optional | Label displayed on the connector — cosmetic only. It does **not** select a path except on a branching node (`decision` / `approval`), which picks its out-edge by label. |
568
568
569
569
### Fault edges — handling a failed node
570
570
@@ -578,6 +578,16 @@ edges: [
578
578
]
579
579
```
580
580
581
+
<Callouttype="warn">
582
+
**`type: 'fault'` is what routes — a label is not.** Writing
583
+
`{ source: 'charge_card', target: 'flag_for_review', label: 'error' }` without
584
+
the type leaves an ordinary edge, and every unconditional out-edge is traversed
585
+
(in parallel) on **success**. The handler then runs whenever the node
586
+
*succeeds*, never when it fails, and the run still aborts on failure. Both
587
+
halves are silent, so `objectstack validate` reports this as
588
+
`flow-error-label-not-fault`.
589
+
</Callout>
590
+
581
591
The handler reads what went wrong from two variables:
582
592
583
593
| Variable | Scope | Use |
@@ -616,6 +626,21 @@ reported success. Re-running changes nothing either: the fix is to correct the
616
626
metadata, which `objectstack validate` will point at.
617
627
</Callout>
618
628
629
+
#### Fault edges vs. `errorHandling.retry`
630
+
631
+
The two recovery mechanisms operate at different scopes and do not compound:
632
+
633
+
|| Scope | On failure |
634
+
| :--- | :--- | :--- |
635
+
|`fault` edge | one node | traversal continues from the handler; the run completes |
636
+
|`errorHandling: { strategy: 'retry' }`| the whole flow | the flow re-runs **from the start**|
637
+
638
+
A failure a fault edge handled is not a flow failure, so it does **not** consume
639
+
a retry. That matters because flow-level retry replays every node that already
640
+
succeeded — a second notification, a second created record. Prefer a fault edge
641
+
where the failure is local; reach for `retry` only when replaying the whole flow
642
+
is genuinely safe.
643
+
619
644
## Variables
620
645
621
646
Flows use variables to pass data between nodes and to/from callers:
0 commit comments