Task Runner Architecture
Relevant source files
The following files were used as context for generating this wiki page:- packages/@n8n/task-runner/src/task-runner.ts
- packages/@n8n/task-runner-python/src/main.py
- packages/cli/src/task-runners/task-broker/task-broker.service.ts
- packages/cli/src/task-runners/task-runner-process-js.ts
- packages/cli/test/integration/task-runners/js-task-runner-execution.integration.test.ts
- packages/cli/src/task-runners/task-managers/data-request-response-builder.ts
The Task Runner architecture in n8n is designed to execute isolated, language-specific tasks, such as user-provided JavaScript or Python code, in separate processes. This approach enhances security and stability by sandboxing code execution away from the main n8n application process. The system is orchestrated by a central TaskBrokerService that manages the lifecycle of different task runners and dispatches tasks to them.
This architecture supports multiple execution modes and languages, with distinct runners for JavaScript and Python, each with its own communication protocol and execution environment.
Core Components
The architecture consists of several key components that work together to manage and execute tasks. The main n8n process communicates with task runners through a broker system.
This diagram shows the high-level interaction between the main n8n CLI application and the specialized task runners.
TaskBrokerService
The TaskBrokerService is the central orchestrator within the main n8n cli package. It is responsible for managing the lifecycle of task runner processes and brokering task requests to the appropriate runner.
- Lifecycle Management: It starts and stops the task runner processes via the
TaskRunnerModule. - Task Dispatching: It receives task requests and forwards them to an available runner based on the task type (e.g.,
javascript,python). - Task Execution: It uses a
TaskRequester(e.g.,LocalTaskRequester) to initiate the task on the selected runner.
Sources: packages/cli/src/task-runners/task-broker/task-broker.service.ts:28-44
TaskRunnerModule
The TaskRunnerModule is responsible for the practical management of task runner child processes. It handles the creation, monitoring, and termination of these processes. When the module starts, it can spin up the necessary runners, and when it stops, it ensures they are properly shut down.
Sources: packages/cli/test/integration/task-runners/js-task-runner-execution.integration.test.ts:18, 102, 106
DataRequestResponseBuilder
Before a task is sent to a runner, the payload is constructed by the DataRequestResponseBuilder. This builder selectively picks required properties from the full workflow and execution context to create a lean TaskData object. This minimizes the amount of data transferred between processes.
Key transformations include:
- Picking a subset of properties from
additionalData. - Simplifying the
workflowobject. - Clearing runtime-specific data like
nodeExecutionStackandwaitingExecutionfromrunExecutionData.
Sources: packages/cli/src/task-runners/task-managers/data-request-response-builder.ts:10, 71-84
Task Execution Flow
The following sequence diagram illustrates the process of executing a JavaScript task, from the initial request to receiving the result.
Language-Specific Runners
The system is designed to be extensible with different types of runners for various languages or environments.
JavaScript Task Runner
The JavaScript runner executes user code in a sandboxed environment to prevent malicious or unstable code from affecting the main n8n instance.
- Process Script: The child process is initiated using the
task-runner-process-js.tsscript. - Communication: It communicates with the main process via Inter-Process Communication (IPC). It listens for
messageevents containing the task data. - Sandboxing: It uses the
vm2library to create a secure virtual machine for executing the code. - Module Control: The runner's environment can be configured to allow or disallow specific built-in and external Node.js modules. This is controlled by environment variables.
Module Permissions
The allowed modules are configured via environment variables, as shown in integration tests.
| Environment Variable | Description | Example |
|---|---|---|
NODE_FUNCTION_ALLOW_BUILTIN | A comma-separated list of built-in Node.js modules to allow. | crypto |
NODE_FUNCTION_ALLOW_EXTERNAL | A comma-separated list of external npm modules to allow. | moment |
Sources: packages/cli/src/task-runners/task-runner-process-js.ts, packages/@n8n/task-runner/src/task-runner.ts:39-58, packages/cli/test/integration/task-runners/js-task-runner-execution.integration.test.ts:109-110
The following test snippet demonstrates how an allowed external module (moment) can be used, while a disallowed one (lodash) will result in an error.
// packages/cli/test/integration/task-runners/js-task-runner-execution.integration.test.ts:169-180
it('should allow importing allowed external module', async () => {
// Act
const result = await runTaskWithCode(`
const moment = require("moment");
return { time: moment("1995-12-25").format("YYYY-MM-DD") }
`);
expect(result).toEqual({
ok: true,
result: { time: '1995-12-25' },
});
});
// packages/cli/test/integration/task-runners/js-task-runner-execution.integration.test.ts:182-192
it('should not allow importing disallowed external module', async () => {
// Act
const result = await runTaskWithCode(`
const lodash = require("lodash");
return [{ obj: lodash.cloneDeep({}) }]
`);
expect(result).toEqual({
ok: false,
error: expect.objectContaining({
message: "Module 'lodash' is disallowed [line 2]",
}),
});
});
Python Task Runner
The Python runner provides an environment for executing Python scripts. Its implementation differs significantly from the JavaScript runner.
- Server: It runs an
aiohttpweb server that listens for tasks. - Communication: It exposes a
/runHTTP endpoint that acceptsPOSTrequests with the task data. - Execution: Upon receiving a task, it uses the built-in
exec()function to execute the Python code. The result is captured from a predefinedresultvariable in the executed code's scope. - Response: The result is serialized to JSON and returned in the HTTP response.
# packages/@n8n/task-runner-python/src/main.py:19-30
async def run(request):
"""
Run a task.
"""
try:
task = await request.json()
code = task["code"]
scope = {"result": None}
exec(code, scope)
return web.json_response({"result": scope["result"]})
except Exception as e:
return web.json_response({"error": str(e)}, status=400)
Sources: [packages/@n8n/task-runner-python/src/main.py]
Summary
The Task Runner architecture provides a robust, secure, and extensible system for executing arbitrary code as part of an n8n workflow. By isolating execution in sandboxed child processes, it protects the core application from potential failures or security risks in user-provided code. The use of a central broker and distinct runners for different languages like JavaScript and Python allows for flexibility and clear separation of concerns. This design is crucial for enabling powerful features like the Code node while maintaining the overall stability and security of the n8n platform.