#issues with setting up a headless node host

1 messages Ā· Page 1 of 1 (latest)

half bronze
#

Running OpenClaw 2026.4.20 on a Windows 11 host. The gateway runs inside a Docker container (Linux). We installed the openclaw CLI globally on the Windows host via npm install -g openclaw@2026.4.20 to set up a headless node host via openclaw node install.
The CLI fails on startup with:
Failed to read config at ~.openclaw\openclaw.json
Error: Cannot find module 'grammy'
The existing openclaw.json on the Windows host is the full gateway config with Telegram enabled. Inside the container, grammy is available as part of the image. On the host npm install, it isn't bundled.
Questions:

Is openclaw node install / openclaw node run expected to load the full gateway config on startup, or is this a side effect of the CLI finding the existing openclaw.json?
Is there a way to run node commands without loading the full config — e.g. a --config flag pointing to a minimal stub, or a flag to skip config loading entirely?
Is the right fix to install grammy globally on the host, or is there a cleaner pattern for running a node host alongside an existing gateway config on the same machine?

vagrant novaBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> āœ… Mark Solution

cursive nebulaBOT
gritty tinselBOT
#

I checked the code path, and this looks like the current behavior, not just a weird side effect.

  • Yes: openclaw node install / openclaw node run still go through the normal CLI config bootstrap first. node is not on the config-bypass list, so the CLI reads the active openclaw.json before dispatching the node command.
  • Also important: for node, plugin CLI registration is actually skipped because it’s a built-in command. So this is not mainly ā€œplugin command registrationā€, it’s the config guard / config load path.
  • node run later loads config again for:
    • gateway auth fallback
    • TLS defaults
    • node-host plugin registry
  • node install itself mostly uses node.json + flags, but it still gets blocked by that early config bootstrap.

For your second question:

  • I do not see a dedicated openclaw node --config ... flag or a ā€œskip config entirelyā€ flag.
  • The clean supported workaround is to use the global isolation mechanisms:
    • openclaw --profile <name> ...
    • or OPENCLAW_CONFIG_PATH + OPENCLAW_STATE_DIR
#

So on Windows, the pattern I’d use is:

$env:OPENCLAW_GATEWAY_TOKEN="<gateway token>"
openclaw --profile nodehost node install --host <gateway-host> --port 18789

and for foreground:

$env:OPENCLAW_GATEWAY_TOKEN="<gateway token>"
openclaw --profile nodehost node run --host <gateway-host> --port 18789

That gives the node host its own:

  • ~/.openclaw-nodehost/openclaw.json
#
  • ~/.openclaw-nodehost/node.json

So it won’t touch the Telegram-enabled host config at ~/.openclaw/openclaw.json.

For the third question:

  • I would not treat ā€œinstall grammy globally on the hostā€ as the right fix.
  • That would just paper over a host-side packaging/runtime-deps mismatch.
  • The cleaner pattern is separate profile/state for the node host.

If you want, you can even keep that profile’s config missing or minimal. OpenClaw’s config is optional; if you create one manually, make it just:

{}

Docs:

half bronze
#

Running OpenClaw 2026.4.20, Windows 11 host, gateway in a Docker container.
Following your earlier advice, we created a minimal config for the node host profile:
powershellSet-Content -Path "$env:USERPROFILE.openclaw-nodehost\openclaw.json" -Value "{}"
Then installed and started the node:
powershellopenclaw --profile nodehost node install --host 127.0.0.1 --port 18789 --display-name "HostOps-Win"
schtasks /End /TN "OpenClaw Node"
schtasks /Run /TN "OpenClaw Node"
The node shows up in openclaw nodes list with a pending pairing request. But every devices approve <requestId> attempt fails with:
GatewayClientRequestError: unknown requestId
This happens even when we run approve within seconds of the request appearing. We've reproduced this consistently across multiple stop/start cycles.
Questions:

Is the node reconnecting in a tight loop and superseding its own pairing request before approve can complete? Is there a way to stabilise it — e.g. a reconnect backoff or a way to freeze the pairing request?
Did the minimal {} config actually take effect for the node process? Is there a way to confirm what config the node host is loading at runtime — e.g. a --verbose flag or a log file?

#

@gritty tinsel

gritty tinselBOT
#

Yep, I think this is very likely not just ā€œthe node reconnects too fastā€.

What the source says:

  • A pending pairing request only gets a new requestId if the auth snapshot changes, mainly public key / device identity, role, or scopes.
  • For a normal node host, role is fixed to node and scopes are fixed to [].
  • So if the requestId keeps going stale, the most likely cause is the node is not using one stable state dir / identity file.

Two important gotchas here:

  1. OpenClaw Node is a fixed Windows task name
    It is not profile-scoped.
    So if you had an older node task already installed, then:

    openclaw --profile nodehost node install
    
#

will not replace it unless you add --force.

  1. The profile isolation is done via persisted env vars:

    • OPENCLAW_STATE_DIR
    • OPENCLAW_CONFIG_PATH

    Those get baked into the installed node.cmd.

What I’d do next

On the Windows host:

openclaw --profile nodehost node status --json

Check these fields in the JSON:

#
  • service.command.sourcePath
  • service.command.environment.OPENCLAW_STATE_DIR
  • service.command.environment.OPENCLAW_CONFIG_PATH

You want them pointing at something like:

  • C:\Users\<you>\.openclaw-nodehost
  • C:\Users\<you>\.openclaw-nodehost\openclaw.json

Then open the task script itself:

Get-Content "<sourcePath from node status>"

That file should contain the same OPENCLAW_STATE_DIR / OPENCLAW_CONFIG_PATH.

#

Most likely fix

Reinstall the node task so you know it is using the nodehost profile:

openclaw node uninstall
openclaw --profile nodehost node install --force --host 127.0.0.1 --port 18789 --display-name "HostOps-Win"
openclaw --profile nodehost node status --json
schtasks /Run /TN "OpenClaw Node"

Also check the identity file

The node identity should live under the same state dir, typically:

#
Get-ChildItem "$env:USERPROFILE\.openclaw-nodehost\identity\device.json"
Get-ChildItem "$env:USERPROFILE\.openclaw-nodehost\node.json"

If that identity file is not stable, the public key changes, and the gateway will keep superseding the pending request.

Reconnect behavior

There is reconnect backoff in the client, but there is no ā€œfreeze pairing requestā€ flag. You shouldn’t need one, because with a stable identity the same pending request should be refreshed, not replaced.

Quick sanity test

Run openclaw devices list twice, a few seconds apart.

  • If the same node keeps getting a different requestId/deviceId, identity/state is rotating.
  • If the request stays the same, approval should work, and then I’d suspect you’re approving against a different gateway instance/config.
#

I dug through the source a bit more, and the most likely culprit is this:

  • openclaw node does still go through normal config bootstrap first.
  • The clean workaround is not installing grammy on the Windows host.
  • The supported pattern is exactly what you tried: run the node under a separate profile so it gets its own OPENCLAW_STATE_DIR + OPENCLAW_CONFIG_PATH.

The catch is:

  • Windows node task name is fixed: OpenClaw Node
  • it is not profile-scoped
  • so if an older task was already installed, openclaw --profile nodehost node install may still leave you with the old task unless you use --force or uninstall first

That also explains the pairing weirdness. A pending request only gets superseded if the auth snapshot changes, mainly:

  • device identity / public key
  • role
  • scopes
#

For a node host, role/scopes are stable, so repeated unknown requestId usually means the node is not actually using one stable state dir / identity file, or devices approve is hitting a different gateway/auth context than devices list.

What I’d check first

openclaw --profile nodehost node status --json

Look at:

  • service.command.sourcePath
  • service.command.environment.OPENCLAW_STATE_DIR
  • service.command.environment.OPENCLAW_CONFIG_PATH

They should point at something like:

  • C:\Users\<you>\.openclaw-nodehost
#
  • C:\Users\<you>\.openclaw-nodehost\openclaw.json

Then inspect the installed script:

Get-Content "<sourcePath from node status>"

What I’d do next

openclaw node uninstall
openclaw --profile nodehost node install --force --host 127.0.0.1 --port 18789 --display-name "HostOps-Win"
openclaw --profile nodehost node status --json
schtasks /Run /TN "OpenClaw Node"
#

Then confirm these exist under the same profile state dir:

  • ...\ .openclaw-nodehost\identity\device.json
  • ...\ .openclaw-nodehost\node.json

Short answer on the pairing question

  • There is reconnect backoff.
  • There is no ā€œfreeze pairing requestā€ flag.
  • If the node is using a stable state dir/identity, reconnects should refresh the same pending request, not constantly replace it.

One more sanity check

Run openclaw devices list twice, a few seconds apart.

  • If the same node keeps getting a new requestId/publicKey, the node identity/state is rotating.
  • If the request stays stable, but approve still says unknown requestId, you’re probably approving against a different gateway instance/profile/auth context.
half bronze
#

thanks Krill it worked

#

Problem 1: grammy missing on host CLI
Installing openclaw globally via npm on Windows picks up the existing openclaw.json (which had Telegram enabled). The host npm package doesn't bundle grammy, causing a hard fail on every CLI command.
Fix: Use a separate profile so the node host gets its own isolated state dir and config:
powershellopenclaw --profile nodehost node install --force --host 127.0.0.1 --port 18789 --display-name "HostOps-Win"
Then create a minimal config for that profile:
powershellSet-Content -Path "$env:USERPROFILE.openclaw-nodehost\openclaw.json" -Value "{}"
Problem 2: devices approve returning unknown requestId
Using openclaw devices approve <id> consistently failed even when the request was visible. Root cause: OpenClaw has two separate pairing stores (devices and nodes) that are disconnected (see GitHub issue #6836).
Fix: Use openclaw nodes approve instead of openclaw devices approve for node pairing requests:
powershelldocker exec openclaw-gateway openclaw nodes list
docker exec openclaw-gateway openclaw nodes approve <requestId>
Problem 3: Service stop/restart unreliable via openclaw CLI
openclaw --profile nodehost node restart and node stop both failed with port-busy errors.
Fix: Use Windows task scheduler directly:
powershellschtasks /End /TN "OpenClaw Node"
Start-Sleep -Seconds 3
schtasks /Run /TN "OpenClaw Node"
Final verification:
powershelldocker exec openclaw-gateway openclaw nodes list
Should show Pending: 0 Ā· Paired: 1 with Last Connect: just now.

gritty tinselBOT
#

Nice, glad we caught it 🦐

Sounds like it was the stale Windows node task / profile state mismatch.

If this fixed it, please right-click the solution message → Apps → āœ… Mark Solution so Answer Overflow can index it for the next person.

#

Nice, thanks for writing this up. One small correction so Answer Overflow doesn’t learn the wrong thing:

  • openclaw nodes approve <requestId> and openclaw devices approve <requestId> are different pairing surfaces in current OpenClaw.
  • So the rule is:
    • if the pending request came from openclaw nodes pending, use openclaw nodes approve
    • if it came from openclaw devices list, use openclaw devices approve

I would not phrase it as ā€œdevices approve is broken because the stores are disconnectedā€. More like: there are two separate approval flows, and in your case the node request you were approving lived in the nodes pairing flow.

The other two takeaways look solid:

  • separate --profile nodehost to avoid the host CLI loading the Telegram-heavy config
  • using schtasks directly is a reasonable Windows workaround if node stop/restart is flaky

If the stop/restart issue is reproducible, that one is worth a GitHub issue: https://github.com/openclaw/openclaw/issues

And if my earlier steer toward devices approve sent you down the wrong branch here, that’s on me, glad you caught it.