On this page
On this page
Tools
Code Mode executors
Code Mode uses Node by default when enabled. Select QuickJS when you
need a hardened guest runtime. Both executors run the same plain JavaScript
cells, expose the same typed tool discovery, and use the same exec and wait
tools. With no global Code Mode setting, automatic per-model activation applies.
Choosing an executor in Labs preserves activation. When writing an object in
config, include enabled: "auto" to retain automatic activation.
An AOT standalone selects the embedded-aot
runtime execution profile. Its Node executor
runs VM code interpreted and returns its normal results. A one-time performance
notice recommends a JIT build for full speed. Builds that disable runtime code
generation, including --disallow-code-generation-from-strings=strict, still
report their runtime restriction with guidance. QuickJS remains a separate guest
runtime with its own embedded WASM and worker qualification; OpenClaw does not
select it automatically.
Choose an executor
| Executor | Intended use | Execution | Suspended state |
|---|---|---|---|
node (default) |
Trusted execution on your Gateway host | Node.js node:vm in a worker thread |
A live worker and JavaScript context |
quickjs |
Hardened guest isolation | QuickJS-WASI in a worker thread, supplied by the bundled executor plugin | A serialized VM snapshot |
Bridge settlement and failure provenance are host-owned under both executors, keeping diagnostics trustworthy regardless of executor isolation.
The intended guest API does not expose Node's filesystem, networking, process,
environment, or module-loading APIs. Module guards and a small set of globals
help keep cells focused on tool orchestration; they do not make node:vm safe
for hostile JavaScript. Use QuickJS when guest isolation is part of your threat
model, and use a separate OS user, container, or host when you need a stronger
host boundary.
QuickJS provides a separate WASM guest with no ambient host access. It can still call powerful tools if your tool policy grants them. Calls made through the Code Mode tool bridge retain OpenClaw's normal policy, approvals, hooks, and session ownership under either executor. These checks mediate tool calls; they cannot contain hostile code that escapes a Node VM context.
Session sandboxing does not move Code Mode cells into the sandbox. Under the Node executor, a cell from a sandboxed session, including one created by a role that requires sandboxing, still runs in the Gateway process. Use QuickJS on Gateways where untrusted people can start sessions on the built-in runtime.
Set the executor
In the web interface, open Settings → Agents & Tools → Labs and use Code Mode executor to choose Node.js (default) or QuickJS (isolated). Activation and executor selection are separate: choosing an executor does not enable Code Mode.
To enable Code Mode with the default Node executor:
For hardened guest isolation, set executor: "quickjs" in the same object.
The bundled plugin ID is code-mode-quickjs; selecting its executor needs no
separate plugin enable step. Explicit executor selection activates this bundled
runtime even when plugins.enabled is false or a restrictive plugins.allow
list omits it. It does not enable other plugins. An explicit plugins.deny
entry or plugins.entries.code-mode-quickjs.enabled: false still blocks it.
An external plugin can supply the quickjs executor choice and remains subject
to the full plugin activation policy. Enable exactly one owner for that choice.
The selectable executor IDs are node and quickjs; node is owned by core.
The selected executor must be available. If it is missing, disabled, denied,
or cannot load, the run fails with runtime_unavailable; OpenClaw never
silently switches to Node or broad direct tool exposure. Check plugin policy
when a configured QuickJS executor is unavailable.
Set agents.entries.<agent>.tools.codeMode.executor to override the global
selection for one agent. An agent that omits it inherits the global choice.
See Code Mode configuration for activation
precedence and limits.
Understand waits and limits
Each cell keeps its selected executor for its entire lifetime, including every
wait. Changing configuration affects new cells; it does not move suspended
JavaScript between engines or replay completed tool calls.
Node retains its live worker context while suspended. QuickJS can release its
worker after serializing the guest and restore the snapshot for wait. Both
continuations are transient: completion, failure, cancellation, expiry, or
Gateway shutdown releases them. Neither survives a Gateway restart.
timeoutMs, output limits, pending-call limits, suspended-run capacity, and
snapshotTtlSeconds apply to both executors. The historical
snapshotTtlSeconds name also controls Node's suspended-context lifetime.
Suspended runs expire after elapsed time, unaffected by system-clock changes.
The host enforces Node's execution deadline by terminating an overdue worker,
including synchronous loops and promise continuations. Output emitted before
the timeout remains available within the configured output limit.
QuickJS additionally checks serialized VM state against maxSnapshotBytes.
Node has no serialized VM snapshot, so that setting does not bound its live
heap. Saved JSON results retain their shared memory/snapshot data allowance.
memoryLimitBytes limits QuickJS guest memory. For Node, it configures a
best-effort V8 worker heap budget across the old and young generations, subject
to engine minimums. That budget includes worker runtime allocations and
excludes external buffers; it is not a per-context heap measurement or security
boundary. Node stops an execution with runtime_unavailable when sustained
major garbage collections leave the worker near its heap limit, then retires
that worker, even when individual collections are cheap. The same guard retires
pressured suspended or warm workers. Output emitted before heap exhaustion or GC retirement remains
available within the configured output limit. Synchronous code that prevents GC notifications remains bounded
by the host execution deadline. Paginate or narrow large tool results and avoid
retaining large arrays; inspect completed actions before retrying. Captured Node
output retains only a bounded prefix, including when a cell emits many entries
without yielding. Fresh cells get fresh globals even when a worker is reused.
Settled input and guest errors release their cell context and keep healthy workers
available for reuse. Native failures, interrupted execution, and cancellation
still retire the worker.
Node keeps at most eight completed workers warm. Their combined nominal heap
budget is 512 MiB at the default 64 MiB per worker, or 8 GiB when every worker
uses the maximum 1 GiB configuration. This is not an RSS limit. Surplus workers
expire three minutes after their last completion. This cooldown keeps recent
burst capacity warm; serial traffic reuses the newest worker without extending
the surplus workers' deadlines. Within that eight-worker budget, the most recently used
idle worker for each compatible host and heap limit remains warm until pressure,
health, runtime rotation, or host shutdown retires it. Workers start only when a
cell needs one; unused Code Mode creates no warm workers. Critical memory
pressure retires all completed idle workers without discarding live continuations.
GC storms above 600 major collections per minute across two consecutive windows
of at least 10 seconds also retire warm workers. The pool admits at most one
storm retirement per minute. Active cells and parked continuations drain through
completion or their existing timeout or expiry before retirement; draining workers
never return to the warm pool. The next cell acquires a healthy worker on demand.
Diagnostic memory samples and stability logs expose the cumulative workerLifecycle
counters. Prometheus exports
openclaw_worker_started_total{script="code-mode-node.worker.js"} and
openclaw_worker_retired_total with bounded retirement reasons: eviction for
the warm-cache limit, idle-expiry for idle deadlines, dispose for explicit
continuation or standalone cleanup, shutdown for host closure, crash for
native worker errors, gc-thrash for the near-limit GC watchdog, and gc-storm
for sustained storm containment. Memory pressure and runtime replacement retain
memory_pressure and rotation.
Retirement counts advance only after native exit; cleanup retries keep the first
recorded reason. Start counts remain grouped by script, since a new worker has
no retirement reason yet.
Node Code Mode leaves garbage collection to V8 instead of requesting inspector full collections between cells or while a cell is suspended. Its heap limit, execution deadline, GC-pressure detection, and worker retirement still apply. Neither executor's setting caps total Gateway RSS. Worker overhead and host-side tool results also consume memory. A retained Node context can use more total host memory than a size-limited QuickJS snapshot. The shared limit of 64 suspended cells and reserved resume slots still applies; there is no separate global memory quota. See Code Mode internals for state ownership and retention.
Upgrade an existing configuration
Doctor and eligible Gateway startup migrations replace an explicit
runtime: "quickjs-wasi" with executor: "quickjs", preserving that explicit
choice. An existing executor setting takes precedence when both fields are
present. Activation and other limits remain unchanged.
The migrated explicit QuickJS choice also works when generic plugins are
disabled or allowlisted, without enabling unrelated plugins. An explicit denial
of code-mode-quickjs still prevents execution.
Configurations that did not explicitly select a runtime use the new Node
default. Set executor: "quickjs" explicitly to retain hardened guest
isolation. The retired runtime field is not a runtime alias; use
openclaw doctor --fix for an older configuration.