Choose Execution Paths
How do you choose which steps run? Use Only when to skip one action, If / Else to choose the first matching branch, and Router to run every matching route. Use Join Paths before their shared action.
Start with Invoice intake’s flow contract and configured actions in the development version. Each alternative below reviews an invoice when Amount is over 1000. In the app, open Workflows → Invoice intake → Dev → flow.
These commands assume a pinned workflow. Start by listing them:
cai workflow list --limit 100 --jsonChoose the exact Invoice intake entry’s data.items[].id as <workflowId>. If names collide, ask which one is meant. Check data.hasMore before concluding it is absent. The CLI can request a larger limit, while MCP caps at 100.
Pin it, then read the outline:
cai use <workflowId> --jsoncai workflow outline --jsonFrom data.flows[].id, note <flowId>. From that flow’s nodes[].id, note <noteNodeId>, <reviewNodeId>, <ordinaryNodeId> and <sharedNodeId> by name. These examples use one action per branch. Scoped MCP calls always require workflowId.
Skip one action without stopping its path
Section titled “Skip one action without stopping its path”In the action’s menu, choose Enable skip condition → Only when. A false condition skips that node and leaves its outputs null. Everything downstream stays eligible. To exclude the entire storage path, use branching.
Read the expression context before setting a condition. These examples assume flow.amount. If you renamed the input, substitute the binding that context advertises throughout.
cai expr context <noteNodeId> --jsoncai node only-when <noteNodeId> --js 'flow.amount > 1000' --jsonMCP uses node_only_when_set with nodeId and js.
Scalar conditions reject && and ||. In a compound rule, entries in a row are joined with AND, and rows are joined with OR. This replacement limits the note to amounts above 1000 and below 2000:
cai node only-when <noteNodeId> --groups-json '[["flow.amount > 1000","flow.amount < 2000"]]' --jsonMCP node_only_when_set takes parsed groups; branch commands use --condition-json, mapped to parsed conditionJson.
Choose one branch and configure its fallback
Section titled “Choose one branch and configure its fallback”Choose Add action, search Actions for If / Else, then edit the initial If’s When. A new If is blank, and a blank If fails execution validation. Else must be unique, last and condition-free. Add branch adds conditions. Move up and Move down change first-match priority.
Find each control before adding it:
cai integration actions 'If / Else' --jsoncai integration actions 'Router' --jsoncai integration actions 'Join Paths' --jsoncai integration actions 'Wait' --jsonMCP uses integration_action_list for searches and integration_action_get for details.
For each matching data.items entry, note nodeSlug and key as its named placeholders below. Inspect If / Else, then add it:
cai integration action --integration <ifSlug> --key <ifComponentKey> --jsoncai node add --flow <flowId> --node <ifSlug> --key <ifComponentKey> --name "Choose invoice review" --jsonNote data.nodeId as <ifNodeId>, then read its branches and bindings:
cai branch list <ifNodeId> --jsoncai expr context <ifNodeId> --jsonNote the data.items[].id values for If and Else as <ifBranchId> and <elseBranchId>. Update the existing If, then connect both branches:
cai branch update <ifNodeId> --route <ifBranchId> --name "Needs review" --js 'flow.amount > 1000' --jsoncai branch connect <ifNodeId> --flow <flowId> --route <ifBranchId> --to <reviewNodeId> --jsoncai branch connect <ifNodeId> --flow <flowId> --route <elseBranchId> --to <ordinaryNodeId> --jsonMCP branch_update and branch_connect use branchId for --route.
To reorder conditions, replace <ids> with every condition ID from branch list, comma-separated and listed exactly once. Exclude Else:
cai branch reorder <ifNodeId> --order '<ids>' --jsonMCP branch_reorder takes an order array.
In the app, draw each branch’s output handle to its action. Ordinary node connect supports main and Error outputs, not branch IDs. If you connect exclusive branches directly to one ordinary action, that action is blocked whenever either incoming path is untaken.
Run every matching route
Section titled “Run every matching route”When the checks overlap, use Router instead, under Routes → Add new route → When. Routes have no order, so the app has no move controls and branch reorder rejects Router. Blank Router conditions are true at runtime, and contrary CLI warnings are stale. Set each condition explicitly.
Inspect Router, then add it:
cai integration action --integration <routerSlug> --key <routerComponentKey> --jsoncai node add --flow <flowId> --node <routerSlug> --key <routerComponentKey> --name "Check invoice routes" --jsonNote data.nodeId as <routerNodeId>, then read its routes and bindings:
cai branch list <routerNodeId> --jsoncai expr context <routerNodeId> --jsonUse the initial data.items[].id as <reviewRouteId>, then name that route and add a second one:
cai branch update <routerNodeId> --route <reviewRouteId> --name "Needs review" --js 'flow.amount > 1000' --jsoncai branch add <routerNodeId> --name "Positive amount" --js 'flow.amount > 0' --jsonNote the add result’s data.id as <positiveRouteId>, then connect both routes:
cai branch connect <routerNodeId> --flow <flowId> --route <reviewRouteId> --to <reviewNodeId> --jsoncai branch connect <routerNodeId> --flow <flowId> --route <positiveRouteId> --to <ordinaryNodeId> --jsonJoin paths before the shared action
Section titled “Join paths before the shared action”Add Join Paths, wire every branch tail into it, and select Mode. It waits for all incoming sources to finish or skip.
| Mode | Active inputs | Result |
|---|---|---|
chooseActivePath | One | Passes that input’s value. |
chooseActivePath | Zero | skipped, skipReason: "no_active_path"; downstream is blocked. Enabling Continue execution when no path is active instead completes with null. |
chooseActivePath | Multiple | Fails. |
passThrough (default) | Any | One field per input; blocked inputs are null. Use for overlapping routes. |
Inspect Join Paths, then add it:
cai integration action --integration <joinSlug> --key <joinComponentKey> --jsoncai node add --flow <flowId> --node <joinSlug> --key <joinComponentKey> --name "Join invoice paths" --jsonNote data.nodeId as <joinNodeId>. With Router, keep the default passThrough and skip the mode and type writes below, but use the same connections. For exclusive paths, set the mode and read both tails:
cai node set <joinNodeId> --prop mode --value chooseActivePath --jsoncai node props <joinNodeId> --jsoncai node get <reviewNodeId> --jsoncai node get <ordinaryNodeId> --jsonThese connections use each tail’s Main output. Select its data.outputs[] entry, excluding Error, and confirm the dataType values agree before you set <pathDataType>. For a join that already exists, inspect its edges first:
cai flow get <flowId> --jsonMatch each join edge’s data.edges[].sourceHandle (output-<outputId>) to that source’s data.outputs[].id. Use that exact output’s type.
pathDataType has no options list. Every connected input whose type resolves must match, including inactive inputs. Unresolved types escape the check, so resolve dynamic outputs and validate.
Set that type, then wire both tails into the join and the join into the shared action:
cai node set <joinNodeId> --prop pathDataType --value '<pathDataType>' --jsoncai node connect --flow <flowId> --from <reviewNodeId> --output main --to <joinNodeId> --jsoncai node connect --flow <flowId> --from <ordinaryNodeId> --output main --to <joinNodeId> --jsoncai node connect --flow <flowId> --from <joinNodeId> --to <sharedNodeId> --jsonMCP uses node_prop_set for node set and node_prop_list for node props.
Delay the shared action
Section titled “Delay the shared action”Add Wait, set Seconds, and wire Join → Wait → shared action. Wait returns true, so keep Join → shared action to preserve its payload. The shared action then waits for both inputs. Wait defaults to 1 second, accepts 0–2,592,000 seconds and uses a durable timer.
Inspect Wait, then add it after the join:
cai integration action --integration <waitSlug> --key <waitComponentKey> --jsoncai node add --flow <flowId> --node <waitSlug> --key <waitComponentKey> --after <joinNodeId> --props '{"seconds":30}' --jsonMCP node_add takes parsed props.
Note data.nodeId as <waitNodeId>, then wire it into the shared action:
cai node connect --flow <flowId> --from <waitNodeId> --to <sharedNodeId> --jsonContinue on error is unavailable on If / Else, so handle errors on the preceding action. Turning it off on an action deletes Error-output edges and the target inputs they supplied. Inspect those dependents afterwards.
Prove each intended path
Section titled “Prove each intended path”A selected route that is not wired can end in a green run with no intended output. Audit your chosen <controlNodeId> (If / Else or Router), fix unwired paths, then validate:
cai branch list <controlNodeId> --jsoncai flow layout <flowId> --jsoncai workflow validate --jsoncai run inputs <flowId> --flow --jsonMCP run_inputs takes the flow ID as nodeIdOrFlowId and flow: true.
From the input result’s data.flowInputs[], note Invoice ID’s id as <invoiceInputId> and the amount input’s id as <amountInputId>. The fixtures use these IDs, so a display-name change does not affect them.
In Dev, choose Test (tooltip: Test current flow), then Run history. Provider actions execute for real, so choose test destinations first.
Run each fixture separately. After each run, note data.workflowExecutionId as <workflowExecutionId> and inspect it before you continue. A failed, paused or timed-out CLI run reports error.detail.workflowExecutionId instead.
cai run flow <flowId> --target dev --inputs '{"<invoiceInputId>":"INV-104","<amountInputId>":120}' --jsoncai exec tree <workflowExecutionId> --jsoncai run flow <flowId> --target dev --inputs '{"<invoiceInputId>":"INV-104","<amountInputId>":1000}' --jsoncai exec tree <workflowExecutionId> --jsoncai run flow <flowId> --target dev --inputs '{"<invoiceInputId>":"INV-105","<amountInputId>":1500}' --jsoncai exec tree <workflowExecutionId> --jsoncai run flow <flowId> --target dev --inputs '{"<invoiceInputId>":"INV-104","<amountInputId>":0}' --jsoncai exec tree <workflowExecutionId> --jsonMCP run_flow takes flowId and parsed inputs; execution_tree takes workflowExecutionId.
Expect Else at 120 and 1000, and Needs review at 1500. With Router, 1500 selects both routes and 0 selects neither. Confirm one shared action after a pass-through join. Distinguish only_when_false, upstream_skipped and no_active_path in run details.