A manual run starts as a canvas action, but the live execution quickly becomes one serialized run object that moves through the editor, the server, and the engine. This page follows that object from the first click to the moment the canvas shows the results, so the runtime path stays clear.

Site map

Background reading

The live manual path

The editor decides what to run

runEntireWorkflow in packages/frontend/editor-ui/src/app/composables/useRunWorkflow.ts starts the trace. It resolves the selected trigger, records telemetry, and calls runWorkflow. That method saves dirty workflow state when needed, gathers the canvas data, and builds IStartRunData through consolidateRunDataAndStartNodes.

A destinationNode changes the request into a partial execution, which belongs on partial executions and dirty nodes . The ordinary manual full-run path leaves runData empty, sends the workflow id, start nodes, and trigger input to the server, and keeps the run focused on the workflow’s natural start point.

runWorkflowApi refuses to start unless the push connection is already open, because the editor needs the live stream before the execution begins. The request then leaves the editor through the workflows store and reaches the server.

The server registers the execution

WorkflowRunner in packages/cli/src/workflow-runner.ts receives the request, registers the execution in ActiveExecutions, and sets up the lifecycle hooks that follow the run. It also establishes the execution context before persistence so the stored record does not keep raw trigger data.

WorkflowRunner.run decides whether the work stays in the main process or moves to queue mode. This page follows the in-process manual path, where runMainProcess prepares the workflow and then hands control to ManualExecutionService.runManually.

ManualExecutionService in packages/cli/src/manual-execution.service.ts chooses the full-run branch when the request does not carry partial run data. It builds a fresh WorkflowExecute instance for the ordinary manual path, and it only rewires a targeted run when a destination node points at a tool and needs TOOL_EXECUTOR_NODE_NAME.

The engine turns the run into state

WorkflowExecute in packages/core/src/execution-engine/workflow-execute.ts owns the live run. Its constructor holds a single IRunExecutionData object, and createRunExecutionData in packages/workflow/src/run-execution-data-factory.ts fills that object with startData, resultData, and executionData, plus the optional top-level fields it sets (parentExecution, resumeToken, waitTill, manualData, pushRef, redactionInfo); nodeExecutionStack, waitingExecution, and waitingExecutionSource live inside executionData rather than beside it.

WorkflowExecute.run seeds executionData.nodeExecutionStack with the first node and records the initial resultData.pinData. That stack and result object stay together for the whole execution; the engine does not hand work off to a separate canvas model.

The loop drives one node after another

processRunExecutionData starts by setting up the execution, establishing the workflow expression isolate, and checking the workflow for issues. It then loops until nodeExecutionStack runs dry, shifting the next IExecuteData entry, calculating the current run index, and stopping if the execution times out or the same node-and-run pair appears twice in a row.

The loop keeps the node order honest. addNodeToBeExecuted uses push or unshift according to workflow.settings.executionOrder, so the engine follows the workflow’s ordering rule rather than guessing from the canvas layout. The helper also holds multi-input work in waitingExecution and waitingExecutionSource until every input arrives.

runNode creates an ExecuteContext from packages/core/src/execution-engine/node-execution-context/node-execution-context.ts. That context gives the node the workflow snapshot, the current node, the current run data, the current run index, the input data, and the additional data helpers that resolve parameters, expressions, and credentials. NodeExecutionContext.getNodeParameter and evaluateExpression resolve against the same live run object that the engine keeps in memory.

Retries stay inside the loop. If a node sets retryOnFail, the engine retries the node up to maxTries, waits between attempts, and keeps the same run object in play. If runNode returns an engine request, handleEngineRequest schedules the requested follow-up work and the loop continues with the new stack state.

The run object keeps the results

After each node finishes, the engine writes an ITaskData entry into resultData.runData[nodeName]. That entry carries the start time, execution index, execution time, status, metadata, and dynamic credential flags, and it becomes the source of truth for everything the editor later shows.

Downstream nodes enter the stack through addNodeToBeExecuted, and nodes with multiple inputs wait until waitingExecution and waitingExecutionSource gather every branch. When the node produces output, lastNodeExecuted updates as progress moves forward. When saveExecutionProgress runs in packages/cli/src/execution-lifecycle/save-execution-progress.ts, it writes that node name back to storage and marks the execution as running.

The canvas styling follows that same result data. The green borders and item counts do not come from a separate canvas execution model; they reflect resultData.runData after the push layer replays it.

The hooks push progress back to the editor

ExecutionLifecycleHooks in packages/core/src/execution-engine/execution-lifecycle-hooks.ts defines the lifecycle moments that the runtime can observe. The CLI wiring in packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts attaches the push, save-progress, persistence, and status handlers to those moments.

hookFunctionsPush sends executionStarted, nodeExecuteBefore, nodeExecuteAfter, nodeExecuteAfterData, executionWaiting, and executionFinished messages through Push whenever pushRef exists. packages/frontend/editor-ui/src/app/stores/pushConnection.store.ts receives those messages, decodes them, and fans them out to the editor listeners that redraw the canvas.

Finalization closes the run

processSuccessExecution decides whether the run ends in success, error, canceled, or waiting. It moves node metadata into the final run data, captures stoppedAt, closes any trigger or webhook cleanup function, and fires workflowExecuteAfter so the CLI layer can persist or prune the execution.

If waitTill stays set, the engine keeps the run alive and the push layer emits executionWaiting; that branch belongs with one execution, many processes . A Wait node can hold the execution open across processes, but the same run object still carries the state.

packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts and packages/cli/src/executions/execution-persistence.ts decide what storage keeps. Save settings can keep the execution, soft-delete a manual run, or prune an unsaved run according to the configured retention rules. Practical breakpoint points include runWorkflowApi, WorkflowRunner.run, WorkflowExecute.run, processRunExecutionData, and hookFunctionsPush.

One execution at a glance

  1. packages/frontend/editor-ui/src/app/composables/useRunWorkflow.ts :: runEntireWorkflow resolves the trigger and starts the manual run.
  2. packages/frontend/editor-ui/src/app/composables/useRunWorkflow.ts :: runWorkflow saves dirty state and builds IStartRunData.
  3. packages/frontend/editor-ui/src/app/composables/useRunWorkflow.ts :: runWorkflowApi sends the request only after the push connection is open.
  4. packages/cli/src/workflow-runner.ts :: WorkflowRunner.run registers the execution and prepares lifecycle hooks.
  5. packages/cli/src/workflow-runner.ts :: runMainProcess builds the workflow and decides between queue mode and the in-process path.
  6. packages/cli/src/manual-execution.service.ts :: runManually selects the full-run branch and creates WorkflowExecute.
  7. packages/workflow/src/run-execution-data-factory.ts :: createRunExecutionData seeds the serializable run object.
  8. packages/core/src/execution-engine/workflow-execute.ts :: processRunExecutionData shifts the stack, runs nodes, and schedules downstream work.
  9. packages/core/src/execution-engine/execution-lifecycle-hooks.ts and packages/frontend/editor-ui/src/app/stores/pushConnection.store.ts move progress back to the editor.
  10. packages/core/src/execution-engine/workflow-execute.ts :: processSuccessExecution and packages/cli/src/executions/execution-persistence.ts settle, store, or prune the execution.

Where to look in the code

  • packages/frontend/editor-ui/src/app/composables/useRunWorkflow.ts — the editor decides full run versus partial run and sends the start request.
  • packages/cli/src/workflow-runner.ts — the server registers the execution, chooses the run location, and wires the hooks.
  • packages/cli/src/manual-execution.service.ts and packages/workflow/src/run-execution-data-factory.ts — the manual request becomes the serializable run object.
  • packages/core/src/execution-engine/workflow-execute.ts — the loop shifts nodes, resolves retries, waits for inputs, and finalizes the run.
  • packages/core/src/execution-engine/node-execution-context/node-execution-context.ts — node code resolves parameters and expressions against the current run state.
  • packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts, packages/cli/src/execution-lifecycle/save-execution-progress.ts, packages/frontend/editor-ui/src/app/stores/pushConnection.store.ts, and packages/cli/src/executions/execution-persistence.ts — progress moves to the editor, storage updates the run, and pruning keeps history under control.

Mermaid sequence diagram

sequenceDiagram
    participant Editor
    participant "REST API"
    participant WorkflowRunner
    participant WorkflowExecute
    participant "node context"
    participant node
    participant runData
    participant "push connection"

    Editor->>"REST API": start manual run
    "REST API"->>WorkflowRunner: request data
    WorkflowRunner->>WorkflowExecute: create and run execution
    WorkflowExecute->>runData: seed run state
    WorkflowExecute->>"node context": build execution context
    "node context"->>node: resolve inputs and expressions
    node->>WorkflowExecute: return node result
    WorkflowExecute->>runData: write node output
    WorkflowExecute->>"push connection": send progress update
    "push connection"->>Editor: replay run event