Debug a Run
Where did the run first go wrong? Open its execution history and find the first node whose result surprised you. Compare its inputs, resolved configuration, and result before you rerun anything. A provider response does not necessarily repeat the values you sent.
Suppose Invoice intake → Check invoice receives INV-105 and Amount 1500 but returns review_needed: false. The example rule is “over 1000 needs review”. Trace where the input, the comparison, or the return mapping changed.
Confirm the version and submitted values
Section titled “Confirm the version and submitted values”In the app: open the workflow’s Run history and select the affected run. Its details open the definition that execution used. Inspect the flow inputs there.
With an assistant: list your workflows:
cai workflow list --limit 100 --jsonNote its data.items[].id as <workflowId> and check data.hasMore. The default limit is 25. Neither CLI nor MCP exposes a next-page cursor. If the target is absent while hasMore is true, use app search and copy its ID from /workflows/<workflowId>/versions/.... MCP uses workflow_list, with limit capped at 100.
This page pins the CLI workflow once, and scoped MCP calls require workflowId. Pin it, then list recent executions:
cai use <workflowId> --jsoncai exec list --limit 10 --jsonMCP uses execution_list with limit. Note the affected execution’s data[].id as <workflowExecutionId>.
Read that execution:
cai exec get <workflowExecutionId> --jsonMCP uses execution_get with workflowExecutionId.
Compare data.workflowVersionStateId and data.input with the expected version and values. In a development flow test, only an omitted input reuses its saved test value. An explicit null, "", false, or 0 overrides it. Live calls use the published definition. While the status is pending, running, or paused, keep reading the same execution ID. Starting another run can duplicate its writes or sends.
How far back you can read depends on the plan code. freePlan, a missing code, or an unknown code gets 7 days. proPlan and starterPlan get 90. maxPlan and custom get no cutoff. Reading an older execution fails with This workflow execution is outside your plan run history limit.
Find the first unexpected node
Section titled “Find the first unexpected node”In the app: expand the flow, subflow, and loop executions. Each node has Inputs, Process, and Outputs.
The flat exec get list contains at most 200 nodes, newest first. Check data.nodeExecutionsTruncated. For a larger run, read the paginated tree instead:
cai exec tree <workflowExecutionId> --limit 50 --jsonMCP uses execution_tree with workflowExecutionId and limit. Its optional filters are status (an array) and flowId for the CLI’s --flow.
The limit defaults to 50, maximum 200. If data.nextCursor is present, use it as <opaque> and repeat until it is absent:
cai exec tree <workflowExecutionId> --cursor <opaque> --jsonThese rows are not a global timeline. They follow the saved workflow’s flow order, then node-execution ID. Use startedAt, flowExecutionId, and parent/subflow structure to locate the first divergence.
--status accepts only these node statuses. If you filter to failures, you hide completed nodes that produced wrong data.
| Node status | Meaning | Check |
|---|---|---|
pending, running | Not finished. | Follow this execution. |
completed | Finished successfully at this level. | Verify values and effects. |
completed_with_error | Finished with a handled or propagated error. | Inspect the error and continuing path. |
failed | Ended unsuccessfully. | Find the first error and earlier writes. |
skipped | Did not execute its action. | Read skipReason. |
cancelled | Cancelled. | Inspect how far it progressed. |
To find skipped nodes, filter with --status skipped. There is no filter for a skip reason:
| skipReason | Cause | Check |
|---|---|---|
only_when_false | This node’s Only when condition was false. | Inspect its operands. |
upstream_skipped | An upstream branch blocked this node. | Trace the blocked path. |
no_active_path | Join Paths received no active path. | Inspect incoming branches. |
all_branches_blocked | Permitted by the response type; the current runner does not emit it. | Inspect the recorded branches without inferring a specific condition. |
An Only when condition that is false supplies null outputs without blocking the ordinary downstream path. An upstream branch block does propagate, so changing this node’s guard will not activate it.
completed_with_error makes cai run flow exit with WORKFLOW_EXECUTION_FAILED. The run still happened. Inspect error.detail.workflowExecutionId instead of dispatching again.
Compare the node’s actual values
Section titled “Compare the node’s actual values”From data.items, note the affected row’s id as <nodeExecutionId>, its flowExecutionId as <flowExecutionId>, and its definition nodeId as <nodeId>. Read the flow execution, then the node execution:
cai exec flow <flowExecutionId> --jsoncai exec node <nodeExecutionId> --jsonMCP uses execution_flow_get with flowExecutionId, then execution_node_get with nodeExecutionId.
A flow summary previews node outputs above 8,000 serialized characters, and shortens each resolved configuration above 1,024. Node JSON already includes the complete output, so --full only expands the non-JSON display. Configurations still shorten above 4,096 characters.
The node runner clears row-level input when it commits a finished node, mid-flow nodes included. A null value does not prove the inputs were absent. In the app, read the persisted input-port values instead. With an assistant, compare the producer’s output with the consumer’s resolvedConfigs.
Check these together:
- Process → Configurations: inspect each
resolvedConfigsentry’sconfigValueProcessed,hasError, andresolutionWarnings. Did Amount resolve to 1500? An empty warning list does not prove the invoice rule passed. Both0andfalseare meaningful values. - Inputs / Outputs: compare received and selected values, including “blocked by condition.”
- Process → Result: inspect the result and
errorMessage, tracing through toreview_needed.
Repair the cause you found
Section titled “Repair the cause you found”The saved definition shown for a run is read-only. To edit the workflow in the app, click the active Run history button and choose Dev. Run controls remain available: Stop for pending/running/paused, Pause for running, and Resume for paused. For expressions, click the affected expression token or Dynamic data to inspect the available data.
For an assistant repair, read the node’s properties:
cai node props <nodeId> --jsonMCP uses node_prop_list. Note the affected data.props[].name as <name>.
Read the current expression and the bindings it can use:
cai expr decompile <nodeId> --prop <name> --jsoncai expr context <nodeId> --prop <name> --jsonMCP uses expr_decompile and expr_context with nodeId and prop.
Save the corrected expression, using those bindings, as fixed-expression.js. Persist it, then inspect data.persistedValue:
cai expr set <nodeId> --prop <name> --js-file fixed-expression.js --jsonMCP uses node_prop_set with nodeId, prop, and inline js instead of a local file.
If a list filter returns unexpected results, check the environment, field, and comparison. A constraint on a missing field is dropped, broadening the results. Empty values normally follow the operator. Ignore empty constraints drops those constraints instead. Inspect actual rows before repeating updates.
If you added invoice storage, identify the run’s environment before reading workflow data:
cai workflow versions --jsonMCP uses workflow_version_list. Match the execution’s workflowVersionId to data.versions[].id and inspect isLiveVersion. Do not infer the environment from isTest, because development tests also set it to false.
In the app, choose the matching Dev or Live → Data tables → invoice. For Dev:
cai data query --type custom.invoice --env dev --limit 50 --jsonFor Live:
cai data query --type custom.invoice --env live --limit 50 --jsonMCP uses data_record_query with type: "custom.invoice", the matching env, and limit: 50. Compare data.items and data.totalCount.
Decide what should happen after an error
Section titled “Decide what should happen after an error”Continue on error records an error and passes data: null downstream. It cannot fix a mapping. If/Else rejects this setting. Enabling it adds an Error output. Disabling it deletes that output, its edges, and the corresponding target inputs. Inspect the error path before you disable it.
Use the node menu’s Continue on error switch, or disable it explicitly:
cai node continue-on-error <nodeId> --disable --jsonMCP uses node_continue_on_error_set with nodeId and enabled: false.
A loop’s Continue to next item if an iteration fails is a separate setting. Inspect every item’s status, error, and outputs. A per-item configuration or expression error still stops the loop.
There is no whole-flow rollback of earlier writes. Update multiple data records rolls back its own batch when any record is missing, belongs to another collection, or fails to update. Earlier nodes’ effects remain. Inspect them before rerunning.
Next: Test a Workflow to rerun the smallest safe scope, then its containing flow. For a Live failure, publish the tested fix, then verify the new Live release once with a safe target. Until you publish, Live triggers keep using the older release. Keep the before-and-after execution IDs.