Related: #136048 (non-Git directory semantics) and #142521 (non-Git worker workspace context; this PR does not implement ephemeral cloud workspaces). ## What Problem This Solves Device-hosted sessions required a Git repository or managed worktree, a constraint inherited from ephemeral cloud sessions, even when a paired personal machine should simply work in an existing non-Git folder. ## User Impact Users can choose **Existing folder on device** in New Session and run a session directly in an existing folder on a paired session-host device. OpenClaw does not create Git metadata, copy the folder to the Gateway, or sync, reconcile, reset, or delete it. - `nodeHost.workerRuns.folders` is node-local consent: unset allows existing directories, a non-empty list restricts selection to those roots and their descendants, and `[]` disables the feature and its capability advertisement. - The session stays pinned to that device: Auto/cloud dispatch, Move, and Continue on Gateway are refused. Attachment staging and the Gateway Files panel are unavailable for these sessions. - Worker HOME stays in private node storage. Files are not locked against external changes. ## Why This Change Was Made - `sessions.create` accepts `deviceFolder: { deviceId, path }` (requires `operator.write`); the binding is stored on the existing session entry and the `device-folder` workspace kind reuses the worker execution and lifecycle owners. - The node owns folder policy and re-validates on every use: absolute or `~` paths only, realpath resolution (symlinks cannot escape a root), no filesystem root, no overlap with private worker storage, and a dev/ino fence between acquisition and execution. One shared rule in `node-workspace-protocol.ts` forbids preparing, resetting, transferring, seeding, or quiescing a device folder, for both ingress parsing and direct node execution. - Gateway shutdown reads the teardown scope from the durable placement, so stopping an attachment with no in-memory tunnel no longer reaches closed worktree admission. - A restored existing-folder create retry is owned by its frozen request; current device capacity or runtime no longer blocks it. - Attachments are refused for device-folder sessions before anything is persisted: `chat.send` rejects them with a clear message, New Session blocks Send while attachments are attached, and worker media preparation never restages historical attachments into a device folder, so one refused file cannot make later turns fail. - Security tradeoff: a session-host node already runs operator-directed agents with exec, so the allow-all default does not widen that boundary. The folder list is a selection guardrail, not an exec sandbox or a boundary against a compromised Gateway. Deployments that contain `operator.write` with `tools.fs.workspaceOnly` should also set a root list; the docs say so. - Control UI selection/path input, plus docs for node configuration, session hosting, operator scopes, and workspace behavior. ## Screenshots Synthetic mocked-Gateway captures (no real paths or tokens). **Before — only the managed/new-workspace choice**  **After — desktop existing-folder input**  **After — mobile layout**  ## Evidence - **Cross-machine E2E on the final feature code:** a disposable Gateway on one Mac (tailnet TLS, token auth, QA mock model) and a session-host node on a second Mac, both on the same exact head, with `folders` unset and an existing non-Git target folder on the second Mac. Pairing and capability advertisement; create + explicit dispatch (`active`, workspace = the device folder); hosted exec wrote `pwd` and `hostname` into the folder, confirmed by independent readback on that Mac; node restart and an unclean Gateway restart, then a second turn appended to the same file; `~` and a Gateway-only path rejected; reclaim + delete left the folder, sentinel, and absence of `.git` unchanged with no copy in Gateway state. - **Isolated single-host E2E (25 checks, all pass):** missing/root/file/relative/private-storage rejections; auto-dispatch rejection; node restarts; clean Gateway shutdown with a live tunnel and with a durable attachment but no tunnel; Gateway restart and resume; symlink escape and outside-root rejection under a root list; `[]` disables the capability. - **Attachment refusal, live:** on the isolated Gateway, a `chat.send` with a text attachment to a device-folder session returns the refusal, the attachment never enters history, and the next text turn and later turns after node and Gateway restarts complete in the folder. - **Authority, live (real Gateway WebSocket, no mocks):** an operator client granted only `operator.read` + `operator.sessions.write` sends `sessions.create` with `deviceFolder` and gets `FORBIDDEN` / `missingScope: operator.write`; no session row exists afterwards and a debugger logpoint on the node records no workspace command for that request. The full `operator.write` client creates and dispatches the same folder (`active`). - **Device revocation during awaited admission, live:** with the node process tree stopped (`SIGSTOP`) while `sessions.create` and `sessions.dispatch` wait on it, `devices remove` succeeds, then `SIGCONT`. Create fails with `Selected device changed during folder validation; retry` and leaves no row; dispatch fails with `Device worker installation connection is no longer current`; the node is then disconnected and its old token is rejected. Known bounded behavior: the read-only folder validation request already delivered to the stopped node still runs once on resume (1 ms after removal) before the close frame arrives; its result is discarded, nothing is persisted, and no session command runs. Unit coverage also revokes the caller (real `captureGatewayDeviceRevocation` capture) and the device during validation. - **Requesting-operator revocation during awaited admission, live:** separate signed operator devices with `operator.write`, real paired node stopped with `SIGSTOP`. Removing the operator while `sessions.create` waits on folder validation: the client gets `gateway closed (4001): device removed`, `sessions.describe` returns `{"session":null}`, the key is absent from `sessions.list`, and the node trace has no worker execution command. Removing a different operator while `sessions.dispatch` waits: the session moves from `provisioning` to `{"state":"failed","terminalReason":"Gateway requester authority changed"}`, `hasActiveRun` is false, and no worker command runs. As with device revocation, one already-delivered read-only folder validation runs on resume and its result is discarded. - **Inline initial turns are refused:** `sessions.create` with `deviceFolder` plus `message`, `task` or `attachments` is rejected before node I/O; the UI already sends the first message through `sessions.send` after dispatch. The three new cases fail with the validator removed (`3 failed | 12 passed`) and pass with it (`25 passed`). - **Upgrade compatibility, live:** the `main` merge-base build created a plain session and an explicit-cwd session (turn `ok`); the PR build on the same state dir lists and describes both with their original IDs, a new turn on the old session completes `ok`, and Doctor exits 0 with no session-row load errors. - **Upgrade from stable `v2026.10.3`, live:** the stable build created a plain session with a completed turn, an explicit-cwd session, a custom model alias and a non-default `thinkingDefault`. Direct startup on the new build exits 78 with "uses schema version 24; … run openclaw doctor --fix" on both this PR and the plain `main` merge base (inherited). After `openclaw doctor --fix --non-interactive` (`v24 -> v26`), the PR Gateway starts, both old sessions list and describe with their original IDs, both settings read back unchanged, a new turn on the old session returns `ok`, and a new device-folder session created after the upgrade dispatches and runs a turn in the folder. One session-event publication warning about an unsettled worker-environment mutation appeared during that dispatch; the turn still succeeded. - **Focused tests:** device-folder node, tunnel, create, protocol, media, submit-gate and draft-state suites pass; the restored-retry and history-restage regressions fail before their fixes. - **Hosted CI:** on the exact head `364a000`, every job passes on the first attempt, and so does the Security Review, except `checks-ui-e2e (8/9)`. That job runs `plugins-lifecycle.e2e.test.ts`, which also fails the same way on plain `main` (see the maintainer override comment). Earlier heads hit flaky qa-lab E2E tests and `main`-side ratchet failures that plain `main` also reproduces. ## Merge Danger Additive optional protocol field and session-entry JSON field; no SQL schema change. Older Gateways reject the new `sessions.create` parameter; older nodes do not advertise the capability, so the option is not offered. Revert removes the feature; existing device-folder sessions would then be unrecognized but their folders are untouched. Co-authored-by: Ayaan Zaidi <hi@obviy.us>
24 KiB
| summary | read_when | title | sidebarTitle | |||
|---|---|---|---|---|---|---|
| Start a headless or service node host, pair it, and use its system commands |
|
Run a node host | Node host |
Remote node host (system.run)
Use a node host when your Gateway runs on one machine and you want commands to execute on another. The model still talks to the gateway; the gateway forwards exec calls to the node host when host=node is selected.
| Role | Responsibility |
|---|---|
| Gateway host | Receives messages, runs the model, routes tool calls. |
| Node host | Executes system.run/system.which on the node machine. |
| Approvals | Enforced on the node host via ~/.openclaw/state/openclaw.sqlite#exec_approvals_config. |
Approval note:
- Approval-backed node runs bind exact request context. The exec path prepares a
systemRunPlanbefore approval; once granted, the gateway forwards that stored plan, not any later caller-edited command/cwd/session fields, and re-validates the working directory before running. - For direct shell/runtime file executions, OpenClaw also best-effort binds one concrete local file operand and denies the run if that file changes before execution.
- If OpenClaw cannot identify exactly one concrete local file for an interpreter/runtime command, approval-backed execution is denied instead of pretending full runtime coverage. Use sandboxing, separate hosts, or an explicit trusted allowlist/full workflow for broader interpreter semantics.
Gateway deployments that cannot host nodes
A Gateway can remain healthy for browser users while node hosting is unavailable. Run openclaw doctor on the Gateway before onboarding nodes, and check these preconditions:
- Machine authentication: Tailscale identity headers do not authenticate node-role connections. In
gateway.auth.mode: "trusted-proxy", a new node also cannot supply the proxy's user identity headers. To use a shared token, switch to token mode and configuregateway.auth.tokenwith a SecretRef; trusted-proxy mode rejects mixed token configuration. A trusted-proxy Gateway can usegateway.auth.passwordonly for clean loopback/direct callers. See trusted-proxy mixed token configuration. - Node onboarding URL: With only the default
gateway.bind: "loopback"and no advertised endpoint,openclaw devices join-codereports that the Gateway is only bound to loopback and recommendsgateway.publicOriginas the primary fix. For public HTTPS ingress, set it to the proxy's reachable origin. Existing Tailscale Serve,gateway.remote.url, bind-derived addresses, and the pairing-specificplugins.entries.device-pair.config.publicUrloverride retain their precedence;publicOriginsupplies the loopback fallback. Remote join URLs require TLS; enabling LAN bind alone does not enable plaintext remote join URLs. Explicitly configured loopback endpoints can produce HTTP join URLs, but the joining machine must be able to reach that loopback endpoint, for example through a local tunnel. Plaintext LAN pairing can use a setup code directly. - Node onboarding support: Join-code creation and
/jredemption are core Gateway operations. They do not require enabling thedevice-pairplugin, even though its retainedpublicUrlconfiguration field can supply an endpoint. See Join codes for the printednpx -y openclaw connect <url> --service --session-hostcommand and the command-only alternative. - Device session runtime: Paired-device runners support the embedded OpenClaw runtime and explicitly authorized Codex
remote-exec; ACPX routes cannot dispatch to a paired device. Codex requirescodex.exec-server.stdio.v1ingateway.nodes.commands.allowplus its normal pairing and invocation approvals. Runtime policy belongs on provider/model routes, not the ignored whole-agent runtime keys. Multi-agent rosters must also setagents.ownership: "explicit". See Codex paired-device placement and runtime policy. - Edge routing: When a reverse proxy or access edge fronts the Gateway, the node must satisfy edge auth on the join request, its main Gateway WebSocket, and the worker WebSocket. Keep WebSocket upgrade enabled for
/__openclaw__/worker. You can instead exempt/j/*and/__openclaw__/workerfrom edge identity auth because both routes enforce their own short-lived credentials. See worker protocol.
For a Cloudflare Access-fronted Gateway:
-
In Cloudflare Zero Trust, create an Access service token. Copy its Client ID and Client Secret when Cloudflare displays them.
-
Add a Service Auth policy that accepts the token on the Access application protecting the Gateway. If
/j/*and/__openclaw__/workerare separate Access applications, add the same policy to both. -
On the node, provide the conventional environment fallback and connect:
export CF_ACCESS_CLIENT_ID="<client-id>" export CF_ACCESS_CLIENT_SECRET="<client-secret>" openclaw connect https://gateway.example/j/<code> --service
The node connection keys are gateway.cloudflareAccess.clientId and gateway.cloudflareAccess.clientSecret; both accept SecretInput values. The environment fallback above persists those keys as env SecretRefs, not copied plaintext. For installed nodes, OpenClaw stores the environment values in the managed service environment file rather than inline in launchd, systemd, or Task Scheduler definitions. Resolved values are bound to the configured Gateway origin and are not followed across redirects. OpenClaw rejects the pair before resolution on plaintext http:// or ws:// routes; credential-free loopback and private-network plaintext behavior is unchanged.
Start a node host (foreground)
On the node machine:
openclaw node run --host <gateway-host> --port 18789 --display-name "Build Node"
For one-paste setup, create a Node host setup link from the Control UI Devices page, then run its copyable command on the node machine:
openclaw node run --pair "oc-pair://<setup-code>"
The link is single-use and expires after 10 minutes. It supplies the endpoint,
bootstrap token, TLS mode, and certificate pin when available. Explicit
gateway flags override the corresponding --pair values. Administrator-minted
bootstrap enrollment approves the device and its first declared command surface,
including system.run and system.which when declared. Later command,
capability, or permission expansion still creates an approval request.
Gateway command policy and the node host's exec approvals
still apply. Local exec approvals default to full with ask: "off"; configure
them before using the link if that access is too broad. See
Node pairing.
node run also accepts --pair, --context-path (Gateway WS context path), --tls, --tls-fingerprint <sha256>, and --node-id (override the legacy client instance ID; this does not reset pairing). On macOS, pass --share-installed-apps to advertise device.apps; sharing is off by default. Use --no-share-installed-apps to disable a previously saved opt-in.
Pass --session-host to enable worker hosting for this foreground process without changing the saved preference. Automatic restarts preserve this choice.
Remote gateway via SSH tunnel (loopback bind)
If the Gateway binds to loopback (gateway.bind=loopback, default in local mode), remote node hosts cannot connect directly. Create an SSH tunnel and point the node host at the local end of the tunnel.
Example (node host -> gateway host):
# Terminal A (keep running): forward local 18790 -> gateway 127.0.0.1:18789
ssh -N -L 18790:127.0.0.1:18789 user@gateway-host
# Terminal B: export the gateway token and connect through the tunnel
export OPENCLAW_GATEWAY_TOKEN="<gateway-token>"
openclaw node run --host 127.0.0.1 --port 18790 --display-name "Build Node" --auth-from-env
Notes:
- Reconnecting to the saved Gateway endpoint uses the paired device token, not ambient credentials from another Gateway.
--auth-from-envexplicitly selectsOPENCLAW_GATEWAY_TOKEN/OPENCLAW_GATEWAY_PASSWORD, including when a paired token exists. Use the same flag withopenclaw node installto persist this choice for a service.- Without a pairing, normal environment/config auth remains available. See node-host authentication for precedence and SecretRef behavior.
Restrict the node command surface
Pass --commands <ids> to openclaw node run, openclaw node install, or
openclaw connect to advertise only an explicit comma-separated list of exact
command IDs. For example, a Session Share node can
publish sessions without exposing execution or other machine capabilities:
openclaw connect <join-url> --service \
--commands openclaw.sessions.list.v1,openclaw.sessions.read.v1
The flag is repeatable. The allowlist is saved in the node's durable machine state, including for installed services; omitting it on a later start keeps the saved list. The node advertises only commands that are both available and allowlisted, with only their required capabilities. Startup fails if no requested command is available. The Gateway pairing approval shows exactly the declared commands; Gateway command policy still applies to invocation.
An explicit allowlist also disables computer use, skill scanning and publication, plugin-tool publication, MCP servers, and worker hosting. An allowlist does not enable a disabled plugin or make an unavailable command available.
Restore the full default surface with openclaw node run --all-commands in
the foreground or openclaw node install --force --all-commands for an
installed service. When enrolling with openclaw connect, add --all-commands
and optionally --service. This durably removes the saved allowlist and
replaces the service's --commands arguments. Do not combine --all-commands
with --commands.
Start a node host (service)
openclaw node install --host <gateway-host> --port 18789 --display-name "Build Node"
openclaw node start
openclaw node restart
node install also accepts --context-path, --tls, --tls-fingerprint, --node-id (legacy client instance ID only), --share-installed-apps / --no-share-installed-apps, --runtime <node|bun> (default: node), and --force to reinstall. Bun requires version 1.4+ with WAL-reset-safe node:sqlite and is an explicit opt-in; Node remains recommended. node status, node stop, and node uninstall are also available.
For npx openclaw node install or npx openclaw connect <join-url> --service,
OpenClaw installs the selected release into <state-dir>/npm before writing
the service. The state directory respects OPENCLAW_STATE_DIR and profiles.
Both launchd and systemd run the durable package after npm clears its _npx
cache. Installation prints the package version, chosen runtime, service command,
and a node update command:
npx -y openclaw@latest node install --force
Use the same profile and state-directory settings. Replace latest with beta
or an exact version when needed. This installs into the same managed prefix,
rewrites the service, and reuses the saved pairing. Reinstalling the same version
repairs missing package files. It does not accumulate a directory per release.
The automatic node-runtime updates below remain independent of this CLI package.
Node shutdown waits for plugin availability watchers and active computer executions
to finish cleanup, and reports failures from those cleanup operations. If a command
reports Node disconnect cleanup failed, reconnect the node to retry disconnect cleanup
before sending another command.
Session-host workspace permissions
A node started with --session-host checks workspace staging before advertising
session capacity. On Unix, the state directory (OPENCLAW_STATE_DIR, or
~/.openclaw) and its resolved ancestors must satisfy the filesystem owner's
checks. Group- or world-writable ancestors without sticky protection prevent
session hosting, even when the node-host directory itself is private.
The node log and environments.list identify the offending directory and the
remediation: run chmod go-w '<reported-path>' on the node, then restart the node
host. Review shared-directory permissions before changing them; alternatively,
move node state under a private directory. OpenClaw does not change ancestor
permissions or bypass staging checks. A trusted sticky system temporary directory
remains supported.
A session-host failure does not disconnect the node or disable its desktop and
other available commands. environments.list keeps a connected node available,
reports sessionHost: false and the hosting diagnostic, and refuses session
placement until the hosting problem is repaired.
Disabled-host reasons are published only when the Gateway advertises
node-worker-host-diagnostics-v1. Older Gateways still receive disabled hosting;
the node log retains the reason. Workspace transfers also report the same
remediation if permissions change after startup.
Automatic node updates
Packaged headless nodes check for updates hourly by default, in both foreground and service mode. They prepare a separate runtime, wait until all node work is idle, then restart and reconnect with the same identity, pairing, and launch options. A node update does not replace the global CLI package or a co-located Gateway. Automatic activations are at least 12 hours apart, and busy work can defer an update indefinitely.
Set nodeHost.autoUpdate.enabled: false on the node to opt out. The shared
update.checkOnStart: false and OPENCLAW_NO_AUTO_UPDATE=1 opt-outs also apply.
Source checkouts, native app nodes, private workers, dev, and
extended-stable installs do not auto-apply. Releases requiring database
migrations defer to the normal update workflow. See
Headless node updates
for the idle-work rules and configuration.
Pair + name
On the Gateway host, approve the device request:
openclaw devices list
openclaw devices approve <deviceRequestId>
If the node retries with changed auth details, re-run openclaw devices list and approve the current requestId.
The node keeps reconnecting while device approval is pending, with exponential backoff capped at 30 seconds. After approval, its next reconnect creates a separate command-surface request. On the Gateway:
openclaw nodes pending
openclaw nodes approve <nodeRequestId>
openclaw nodes describe --node <id|name|ip>
If an older client already reports that reconnect is paused, restart the
installed node with openclaw node restart, or stop and rerun its foreground
openclaw node run command once.
The device and node request IDs are distinct. An initial unapproved surface has no effective commands. SSH-verified and bootstrap enrollment can approve the first surface automatically; trusted-network device approval alone does not. Later expansions need approval, while previously approved commands that remain declared and allowed can still run. Gateway command policy and node-local exec approvals remain separate gates.
Naming options:
--display-nameonopenclaw node run/openclaw node install(persists in the sharednodeHost.configSQLite machine-state value alongside the client instance ID and Gateway connection metadata).openclaw nodes rename --node <id|name|ip> --name "Build Node"(gateway override).
Headless identity state
The headless node keeps three separate state records in shared SQLite:
~/.openclaw/state/openclaw.sqlite(config_machine_state, keynodeHost.config): the client instance ID, display name, and Gateway connection metadata.~/.openclaw/state/openclaw.sqlite(device_identities, keyprimary): the signed device keypair and derived cryptographic device ID.~/.openclaw/state/openclaw.sqlite(device_auth_tokens): paired device auth tokens keyed by cryptographic device ID and role.
For a signed node, the Gateway uses the cryptographic device ID for pairing and
node routing. The client instance ID is only connection metadata. Changing
--node-id or migrating a retired node.json therefore does not reset pairing. See
Identity and pairing state for the
supported revoke-and-re-pair flow and upgrade notes.
Retired identity/device.json and identity/device-auth.json files are
Doctor-owned migration inputs. Stop the node host and run
openclaw doctor --fix; Doctor imports and verifies their rows in SQLite before
removing the old files.
The headless node and macOS app worker check this state before preparing
capabilities. Pending device auth, exec approvals, or a missing stored
identity with retired identity data requires Doctor; startup preserves the
inputs and does not create replacement keys or import execution policy. A valid
stored identity keeps precedence over stale identity/device.json data.
System commands (node host / mac node)
The macOS node and headless node host both expose system.run.prepare, system.run, system.which, and system.execApprovals.get/set; the macOS node also exposes system.notify.
Examples:
openclaw nodes notify --node <idOrNameOrIp> --title "Ping" --body "Gateway ready"
openclaw nodes invoke --node <idOrNameOrIp> --command system.which --params '{"bins":["git"]}'
Notes:
system.runreturns stdout/stderr/exit code in the payload.- Shell execution goes through the
exectool withhost=node; the separatenodes.runexecution path was removed in 2026.3.31.nodesremains the direct-RPC surface for explicit node commands. nodes invokedoes not exposesystem.runorsystem.run.prepare; those stay on the exec path only.- The exec path reads the node policy and prepares a
systemRunPlan. Full/off execution resolves working-directory aliases without adding approval-only script checks. When caller or node policy requires approval binding, stricter path and script checks remain in place. Once an approval is granted, the gateway forwards that stored plan, not any later caller-edited command/cwd/session fields. system.notifyrespects notification permission state on the macOS app; supports--priority <passive|active|timeSensitive>and--delivery <system|overlay|auto>.- Unrecognized node
platform/deviceFamilymetadata uses a conservative default allowlist that excludessystem.runandsystem.which. If you intentionally need those commands for an unknown platform, add them explicitly viagateway.nodes.commands.allow. - A
system.runrequest supportscwd, anenvmap,timeoutMs, andneedsScreenRecording— these are fields of the request payload carried on the exec path (see above), notnodes invokeCLI flags. - For shell wrappers (
bash|sh|zsh ... -c/-lc), request-scopedenvvalues are reduced to an explicit allowlist (TERM,LANG,LC_*,COLORTERM,NO_COLOR,FORCE_COLOR). - For allow-always decisions in allowlist mode, known dispatch wrappers (
env,flock,nice,nohup,stdbuf,timeout) persist inner executable paths instead of wrapper paths. If unwrapping is not safe, no allowlist entry is persisted automatically. - On Windows node hosts in allowlist mode, shell-wrapper runs via
cmd.exe /crequire approval (allowlist entry alone does not auto-allow the wrapper form). - Node hosts ignore
PATHoverrides in theenvobject and strip a large, maintained set of interpreter/shell startup variables (for exampleNODE_OPTIONS,PYTHONPATH,BASH_ENV,DYLD_*,LD_*) before running a command. If you need extra PATH entries, configure the node host service environment (or install tools in standard locations) instead of passingPATHviaenv. - On macOS node mode,
system.runis gated by exec approvals in the macOS app (Settings → Exec approvals). Ask/allowlist/full behave the same as the headless node host; denied prompts returnSYSTEM_RUN_DENIED. - On headless node host,
system.runis gated by the local SQLite exec approvals row; on macOS specifically, see the exec-host routing env vars under Headless node host below.
Headless node host (cross-platform)
OpenClaw can run a headless node host (no UI) that connects to the Gateway WebSocket and exposes system.run / system.which. This is useful on Linux/Windows or for running a minimal node alongside a server.
Start it:
openclaw node run --host <gateway-host> --port 18789
Notes:
- Device pairing and command-surface approval are still required; follow Pair + name through both stages.
- Client instance metadata, signed device identity, and pairing auth use separate state records; see Headless identity state.
- Exec approvals are enforced locally via
~/.openclaw/state/openclaw.sqlite#exec_approvals_config(see Exec approvals). - On macOS, the headless node host executes
system.runlocally by default. SetOPENCLAW_NODE_EXEC_HOST=appto require the companion app exec host, with no local fallback.OPENCLAW_NODE_EXEC_FALLBACKdoes not change current routing. - Add
--tls/--tls-fingerprintwhen the Gateway WS uses TLS.
Mac node mode
- The macOS menubar app connects to the Gateway WS server as a node (so
openclaw nodes …works against this Mac). - In remote mode, the app opens an SSH tunnel for the Gateway port and connects to
localhost.
Existing-folder session consent
nodeHost.workerRuns.folders is a node-local array of existing directory roots.
When unset, any existing directory is allowed, except filesystem roots and
directories overlapping private worker storage. A non-empty list restricts access
to those roots and their descendants; [] explicitly disables existing-folder
sessions and capability advertising, even when nodeHost.workerRuns.enabled is
true. Configure restrictions on the device, not the Gateway:
{
nodeHost: {
workerRuns: {
enabled: true,
folders: ["~/src/tries"],
},
},
}
Roots and selected folders accept absolute paths or a leading ~. The node
resolves both through realpath on every operation. A selected directory must
remain inside a configured root when restrictions are set; a symlink cannot
escape that root. Files, missing directories, filesystem roots, and directories
overlapping OpenClaw's private worker storage are rejected in every mode. Prefer
narrow roots rather than your home directory when restricting access. Restart a foreground node process after changing its
local configuration so it advertises the updated consent.
The unrestricted default does not expand the existing session-host exec trust boundary: operators can already run agents with exec on this device. This list is a folder-selection guardrail for the Gateway's session requests, not an exec sandbox or a boundary against a compromised Gateway.
An existing-folder session stays pinned to that device. OpenClaw does not copy, reconcile, reset, or remove the selected folder. Worker HOME and metadata use private node storage. See Session hosting for runtime support and the New Session flow.