Skip to content

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.

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:

Terminal window
cai workflow list --limit 100 --json

Note 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:

Terminal window
cai use <workflowId> --json
cai exec list --limit 10 --json

MCP uses execution_list with limit. Note the affected execution’s data[].id as <workflowExecutionId>.

Read that execution:

Terminal window
cai exec get <workflowExecutionId> --json

MCP 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.

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:

Terminal window
cai exec tree <workflowExecutionId> --limit 50 --json

MCP 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:

Terminal window
cai exec tree <workflowExecutionId> --cursor <opaque> --json

These 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 statusMeaningCheck
pending, runningNot finished.Follow this execution.
completedFinished successfully at this level.Verify values and effects.
completed_with_errorFinished with a handled or propagated error.Inspect the error and continuing path.
failedEnded unsuccessfully.Find the first error and earlier writes.
skippedDid not execute its action.Read skipReason.
cancelledCancelled.Inspect how far it progressed.

To find skipped nodes, filter with --status skipped. There is no filter for a skip reason:

skipReasonCauseCheck
only_when_falseThis node’s Only when condition was false.Inspect its operands.
upstream_skippedAn upstream branch blocked this node.Trace the blocked path.
no_active_pathJoin Paths received no active path.Inspect incoming branches.
all_branches_blockedPermitted 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.

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:

Terminal window
cai exec flow <flowExecutionId> --json
cai exec node <nodeExecutionId> --json

MCP 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:

  1. Process → Configurations: inspect each resolvedConfigs entry’s configValueProcessed, hasError, and resolutionWarnings. Did Amount resolve to 1500? An empty warning list does not prove the invoice rule passed. Both 0 and false are meaningful values.
  2. Inputs / Outputs: compare received and selected values, including “blocked by condition.”
  3. Process → Result: inspect the result and errorMessage, tracing through to review_needed.

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:

Terminal window
cai node props <nodeId> --json

MCP uses node_prop_list. Note the affected data.props[].name as <name>.

Read the current expression and the bindings it can use:

Terminal window
cai expr decompile <nodeId> --prop <name> --json
cai expr context <nodeId> --prop <name> --json

MCP 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:

Terminal window
cai expr set <nodeId> --prop <name> --js-file fixed-expression.js --json

MCP 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:

Terminal window
cai workflow versions --json

MCP 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:

Terminal window
cai data query --type custom.invoice --env dev --limit 50 --json

For Live:

Terminal window
cai data query --type custom.invoice --env live --limit 50 --json

MCP uses data_record_query with type: "custom.invoice", the matching env, and limit: 50. Compare data.items and data.totalCount.

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:

Terminal window
cai node continue-on-error <nodeId> --disable --json

MCP 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.