Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
After upgrading from 2026.4.26 → 2026.5.3-1, the bundled file-transfer plugin's node-side commands (dir.list, file.fetch, dir.fetch, file.write) are never advertised by the Windows node host during the live WebSocket handshake, even though the plugin manifest has enabledByDefault: true, the plugin is enabled in both gateway and node-local openclaw.json (plugins.entries.file-transfer.enabled = true + plugins.allow includes file-transfer), and the gateway side correctly registers the plugin (12 plugins listening, file_fetch/dir_list tools surfaced to agents). All gateway invocations of these tools fail with node command not allowed: the node (platform: win32) does not support "dir.list", which means nodeSession.commands (live handshake payload) does not include the plugin's nodeHostCommands entries.
Steps to reproduce
- Pair a Windows 11 node to a Linux ARM64 (Ubuntu 24.04) gateway running
openclaw@2026.5.3-1 via TLS over Tailscale: openclaw node install --host <gateway>.tailnet --port 8443 --tls --tls-fingerprint <fp> --display-name <name>.
- On the gateway, ensure config has:
"plugins": {
"allow": ["file-transfer", ...],
"entries": { "file-transfer": { "enabled": true, "config": { "nodes": { "<name>": { "ask": "off", "allowReadPaths": ["C:\\Users\\<u>\\Desktop"], "allowWritePaths": ["C:\\Users\\<u>\\Desktop"], "denyPaths": [...], "maxBytes": 16777216, "followSymlinks": false } } } } }
},
"gateway": { "nodes": { "allowCommands": ["file.fetch", "file.write", "dir.list", "dir.fetch"] } }
- On the Windows node-local
~/.openclaw/openclaw.json, ensure:
"plugins": {
"allow": ["browser", "device-pair", "file-transfer", "openai"],
"entries": { "file-transfer": { "enabled": true } }
}
- Restart gateway service AND node host service (Scheduled Task or
openclaw node restart).
- Confirm
openclaw plugins list on Windows shows File Transfer ... enabled, and the plugin physical path <npm-prefix>/node_modules/openclaw/dist/extensions/file-transfer/ exists.
- From any agent chat with the gateway, request the agent to call
dir_list against the paired Windows node.
Observed gateway log line:
[ws] ⇄ res ✗ node.invoke 1ms errorCode=INVALID_REQUEST errorMessage=node command not allowed: the node (platform: win32) does not support "dir.list" conn=... id=...
Expected behavior
Per extensions/file-transfer/index.ts, the plugin exports nodeHostCommands: [{command: "file.fetch", ...}, {command: "dir.list", ...}, {command: "dir.fetch", ...}, {command: "file.write", ...}]. Per src/node-host/runner.ts, the node host should call:
const cfg = getRuntimeConfig();
await ensureNodeHostPluginRegistry({ config: cfg, env: process.env });
const pluginNodeHost = listRegisteredNodeHostCapsAndCommands();
// ...
commands: [...NODE_SYSTEM_RUN_COMMANDS, ...NODE_EXEC_APPROVALS_COMMANDS, ...pluginNodeHost.commands]
With file-transfer enabled in node-local config and enabledByDefault: true in the manifest, pluginNodeHost.commands should include ["dir.list", "file.fetch", "dir.fetch", "file.write"], and the connect-handshake commands payload should include them. The gateway gate at src/gateway/server-methods/nodes.ts would then pass:
const allowed = isNodeCommandAllowed({
command,
declaredCommands: nodeSession.commands, // includes dir.list ✅
allowlist, // includes dir.list ✅ (via gateway.nodes.allowCommands)
});
Actual behavior
nodeSession.commands includes only ["browser.proxy", "system.run", "system.run.prepare", "system.which"] (the NODE_SYSTEM_RUN_COMMANDS set plus the bundled browser.proxy). It never includes dir.list, file.fetch, dir.fetch, file.write, even though:
plugins.allow and plugins.entries.file-transfer.enabled: true are set in the node-local openclaw.json.
openclaw plugins list on Windows reports File Transfer ... enabled (loaded successfully on the local CLI registry).
paired.json on the gateway has been refreshed (via openclaw node install --force) and contains the expected caps: [browser, file-transfer, system] + commands: [browser.proxy, dir.fetch, dir.list, file.fetch, file.write, system.run, system.run.prepare, system.which].
- Gateway-side
gateway.nodes.allowCommands correctly opts the dangerous commands into the allowlist (without this, the error is is not in the allowlist for platform "win32"; with it, the error becomes the node (platform: win32) does not support, confirming gates are independent).
The error stays the same across multiple node restarts (Stop/Start ScheduledTask, openclaw node install --force, manual node ... node run ... invocation, fresh node.cmd regeneration). The node host process is alive (PID confirmed), connected (paired.json lastConnectedAtMs updates on each reconnect), and apparently functional for system.run calls — only the plugin-derived commands are missing from the live handshake.
OpenClaw version
2026.5.3-1 (2eae30e) on both gateway (Linux ARM64) and node (Windows 11)
Operating system
Gateway: Ubuntu 24.04 LTS, kernel 6.17.0-1010-oracle, aarch64
Node: Windows 11 Pro 23H2 (10.0.22631.6199), x64
Install method
npm global on both sides (npm install -g openclaw@2026.5.3-1)
Model
google/gemini-3.1-pro-preview-customtools (via openrouter; tool call routing path is the standard agent loop, model choice is incidental — the failure is at the gateway↔node WS layer, not the model layer)
Provider / routing chain
Agent (gateway) → openrouter → google → tool-call → gateway server-methods/nodes.ts → gate fails before node.invoke is forwarded
Additional provider/model setup details
The failing tool is gateway-resident (file-transfer plugin's dir_list tool) which calls node.invoke with command: "dir.list" at the WS layer. Provider routing for the agent is healthy (exec, web_search_v2, etc. work fine in the same session). The failure path is purely:
agent → tool dispatcher → gateway nodes.ts isNodeCommandAllowed → reject (declaredCommands missing)
Additional context (full troubleshooting timeline)
I spent ~50 minutes reading source, applying every documented fix, and validating each gate independently. Summary of what works and what does not:
- ✅
gateway.nodes.allowCommands gate (Gate 1) — passes after adding file.fetch, file.write, dir.list, dir.fetch to gateway config (dangerous: true commands per node-command-policy.ts require explicit opt-in; PLATFORM_DEFAULTS for windows does not include them).
- ✅
paired.json cache (storage) — manually edited and confirmed refreshed after openclaw node install --force (caps, commands, version all current).
- ❌
declaredCommands gate (Gate 2) — node host process never advertises plugin commands on the live handshake.
Per src/plugins/runtime/runtime-registry-loader.ts the loader is invoked with scope: "all" and config: <node-local-cfg>. The node-local config has plugins.allow: ["browser","device-pair","file-transfer","openai"] and plugins.entries.file-transfer.enabled: true. Same plugin manifest works on the gateway side (file-transfer is one of the 12 plugins listening; wiki_search etc. are also registered on the gateway).
Hypotheses I could not validate without source-side instrumentation:
ensureNodeHostPluginRegistry may apply a scope filter that drops plugins whose register(api) requires a gateway-only API surface, even though their nodeHostCommands array could be registered in isolation.
- The plugin loader may require
OPENCLAW_BUNDLED_PLUGINS_DIR env var to be set in node host context — node.cmd generated by openclaw node install --force does not set it.
pluginNodeHost = listRegisteredNodeHostCapsAndCommands() may be returning {caps: [], commands: []} because the registry is loaded but not committed before the connect payload is built (race / lazy import).
Workaround: use system.run (which is in the platform default and gets advertised) to invoke powershell -Command "Get-ChildItem ...". Loses path policy / byte-cap / approval flow that file-transfer provides.
Happy to instrument and re-test if a maintainer can suggest where to add a console.error('[node-host] plugin nodeHostCommands:', pluginNodeHost.commands) print to confirm which hypothesis is real.
Sanitized config snippets reproducing the issue
Gateway-side (/root/.openclaw/openclaw.json):
{
"plugins": {
"allow": ["acpx","active-memory","browser","copilot-proxy","device-pair","diffs","discord","file-transfer","firecrawl","github-copilot","google","llm-task","lobster","memory-core","memory-wiki","open-prose","openai","opencode","opencode-go","openrouter","pinchtab","searxng-search","tavily","telegram","thread-ownership","voice-call"],
"entries": {
"file-transfer": {
"enabled": true,
"config": {
"nodes": {
"<node-display-name>": {
"ask": "off",
"allowReadPaths": ["C:\\Users\\<user>\\Desktop"],
"allowWritePaths": ["C:\\Users\\<user>\\Desktop"],
"denyPaths": ["C:\\Users\\<user>\\.openclaw","C:\\Users\\<user>\\.ssh","C:\\Users\\<user>\\.aws","C:\\Users\\<user>\\AppData"],
"maxBytes": 16777216,
"followSymlinks": false
}
}
}
}
}
},
"gateway": {
"nodes": {
"allowCommands": ["dir.fetch","dir.list","file.fetch","file.write"]
}
}
}
Windows-local (C:\Users\<user>\.openclaw\openclaw.json):
{
"plugins": {
"allow": ["browser","device-pair","file-transfer","openai"],
"entries": {
"browser": { "enabled": true },
"openai": { "enabled": true },
"file-transfer": { "enabled": true }
}
},
"gateway": {
"mode": "remote",
"remote": { "url": "wss://<gateway>.tailnet:8443", "transport": "direct" }
}
}
paired.json on gateway (verified post node install --force):
{
"<node-id-hash>": {
"displayName": "<node-display-name>",
"platform": "win32",
"version": "2026.5.3-1",
"caps": ["browser", "file-transfer", "system"],
"commands": ["browser.proxy", "dir.fetch", "dir.list", "file.fetch", "file.write", "system.run", "system.run.prepare", "system.which"]
}
}
Repro is fully deterministic in my environment — every restart of the node host yields the same broken handshake.
Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
After upgrading from
2026.4.26→2026.5.3-1, the bundledfile-transferplugin's node-side commands (dir.list,file.fetch,dir.fetch,file.write) are never advertised by the Windows node host during the live WebSocket handshake, even though the plugin manifest hasenabledByDefault: true, the plugin isenabledin both gateway and node-localopenclaw.json(plugins.entries.file-transfer.enabled = true+plugins.allowincludesfile-transfer), and the gateway side correctly registers the plugin (12 plugins listening, file_fetch/dir_list tools surfaced to agents). All gateway invocations of these tools fail withnode command not allowed: the node (platform: win32) does not support "dir.list", which meansnodeSession.commands(live handshake payload) does not include the plugin'snodeHostCommandsentries.Steps to reproduce
openclaw@2026.5.3-1via TLS over Tailscale:openclaw node install --host <gateway>.tailnet --port 8443 --tls --tls-fingerprint <fp> --display-name <name>.~/.openclaw/openclaw.json, ensure:openclaw node restart).openclaw plugins liston Windows showsFile Transfer ... enabled, and the plugin physical path<npm-prefix>/node_modules/openclaw/dist/extensions/file-transfer/exists.dir_listagainst the paired Windows node.Observed gateway log line:
Expected behavior
Per
extensions/file-transfer/index.ts, the plugin exportsnodeHostCommands: [{command: "file.fetch", ...}, {command: "dir.list", ...}, {command: "dir.fetch", ...}, {command: "file.write", ...}]. Persrc/node-host/runner.ts, the node host should call:With
file-transferenabled in node-local config andenabledByDefault: truein the manifest,pluginNodeHost.commandsshould include["dir.list", "file.fetch", "dir.fetch", "file.write"], and the connect-handshakecommandspayload should include them. The gateway gate atsrc/gateway/server-methods/nodes.tswould then pass:Actual behavior
nodeSession.commandsincludes only["browser.proxy", "system.run", "system.run.prepare", "system.which"](theNODE_SYSTEM_RUN_COMMANDSset plus the bundledbrowser.proxy). It never includesdir.list, file.fetch, dir.fetch, file.write, even though:plugins.allowandplugins.entries.file-transfer.enabled: trueare set in the node-localopenclaw.json.openclaw plugins liston Windows reportsFile Transfer ... enabled(loaded successfully on the local CLI registry).paired.jsonon the gateway has been refreshed (viaopenclaw node install --force) and contains the expectedcaps: [browser, file-transfer, system]+commands: [browser.proxy, dir.fetch, dir.list, file.fetch, file.write, system.run, system.run.prepare, system.which].gateway.nodes.allowCommandscorrectly opts the dangerous commands into the allowlist (without this, the error isis not in the allowlist for platform "win32"; with it, the error becomesthe node (platform: win32) does not support, confirming gates are independent).The error stays the same across multiple node restarts (Stop/Start ScheduledTask,
openclaw node install --force, manualnode ... node run ...invocation, fresh node.cmd regeneration). The node host process is alive (PID confirmed), connected (paired.jsonlastConnectedAtMsupdates on each reconnect), and apparently functional forsystem.runcalls — only the plugin-derived commands are missing from the live handshake.OpenClaw version
2026.5.3-1 (2eae30e) on both gateway (Linux ARM64) and node (Windows 11)
Operating system
Gateway: Ubuntu 24.04 LTS, kernel 6.17.0-1010-oracle, aarch64
Node: Windows 11 Pro 23H2 (10.0.22631.6199), x64
Install method
npm global on both sides (
npm install -g openclaw@2026.5.3-1)Model
google/gemini-3.1-pro-preview-customtools (via openrouter; tool call routing path is the standard agent loop, model choice is incidental — the failure is at the gateway↔node WS layer, not the model layer)
Provider / routing chain
Agent (gateway) → openrouter → google → tool-call → gateway server-methods/nodes.ts → gate fails before node.invoke is forwarded
Additional provider/model setup details
The failing tool is gateway-resident (
file-transferplugin'sdir_listtool) which callsnode.invokewithcommand: "dir.list"at the WS layer. Provider routing for the agent is healthy (exec,web_search_v2, etc. work fine in the same session). The failure path is purely:Additional context (full troubleshooting timeline)
I spent ~50 minutes reading source, applying every documented fix, and validating each gate independently. Summary of what works and what does not:
gateway.nodes.allowCommandsgate (Gate 1) — passes after addingfile.fetch, file.write, dir.list, dir.fetchto gateway config (dangerous: truecommands pernode-command-policy.tsrequire explicit opt-in; PLATFORM_DEFAULTS forwindowsdoes not include them).paired.jsoncache (storage) — manually edited and confirmed refreshed afteropenclaw node install --force(caps,commands,versionall current).declaredCommandsgate (Gate 2) — node host process never advertises plugin commands on the live handshake.Per
src/plugins/runtime/runtime-registry-loader.tsthe loader is invoked withscope: "all"andconfig: <node-local-cfg>. The node-local config hasplugins.allow: ["browser","device-pair","file-transfer","openai"]andplugins.entries.file-transfer.enabled: true. Same plugin manifest works on the gateway side (file-transfer is one of the 12 plugins listening;wiki_searchetc. are also registered on the gateway).Hypotheses I could not validate without source-side instrumentation:
ensureNodeHostPluginRegistrymay apply a scope filter that drops plugins whoseregister(api)requires a gateway-only API surface, even though theirnodeHostCommandsarray could be registered in isolation.OPENCLAW_BUNDLED_PLUGINS_DIRenv var to be set in node host context —node.cmdgenerated byopenclaw node install --forcedoes not set it.pluginNodeHost = listRegisteredNodeHostCapsAndCommands()may be returning{caps: [], commands: []}because the registry is loaded but not committed before the connect payload is built (race / lazy import).Workaround: use
system.run(which is in the platform default and gets advertised) to invokepowershell -Command "Get-ChildItem ...". Loses path policy / byte-cap / approval flow thatfile-transferprovides.Happy to instrument and re-test if a maintainer can suggest where to add a
console.error('[node-host] plugin nodeHostCommands:', pluginNodeHost.commands)print to confirm which hypothesis is real.Sanitized config snippets reproducing the issue
Gateway-side (
/root/.openclaw/openclaw.json):Windows-local (
C:\Users\<user>\.openclaw\openclaw.json):paired.json on gateway (verified post
node install --force):{ "<node-id-hash>": { "displayName": "<node-display-name>", "platform": "win32", "version": "2026.5.3-1", "caps": ["browser", "file-transfer", "system"], "commands": ["browser.proxy", "dir.fetch", "dir.list", "file.fetch", "file.write", "system.run", "system.run.prepare", "system.which"] } }Repro is fully deterministic in my environment — every restart of the node host yields the same broken handshake.