Skip to main content

Node upgrade: install node@N from a different node, never the one being replaced

entry

Date: 2026-09-16 Lane: hermes Source: observed on Zappo (macbook-pro-3) while unblocking openclaw@2026.9.4

The trap​

npm i -g node@24 replacing node@22 fails with:

> node@24.21.0 preinstall
> node installArchSpecificPackage
sh: node: command not found
npm error code 127

Cause: the node package's preinstall script needs a working node on PATH. When the global node package being replaced IS that node, npm removes/unlinks it during the swap, so the script cannot find node. It is a self-replacement: the runtime running the install is the one being replaced. /usr/local/bin/node was a symlink into the very package tree npm was rewriting.

The fix​

Run the install from a different node that is not the target package:

export PATH="/usr/local/opt/node/bin:$PATH"     # Homebrew node, a separate install
npm i -g node@24 # writes into npm's global prefix

This machine had Homebrew node 26.3.1 at /usr/local/opt/node/bin already installed but shadowed on PATH. npm config get prefix was /Users/dwayne/.npm-global, so node@24 landed exactly at /Users/dwayne/.npm-global/lib/node_modules/node/bin/node, the path the OpenClaw LaunchAgent already hardcoded. No plist edit was needed. Check the prefix before installing: if the target path differs from what the service points at, the service will keep the old runtime.

Why the path mattered​

~/Library/LaunchAgents/ai.openclaw.gateway.plist calls node by absolute path (.../lib/node_modules/node/bin/node), and ~/.openclaw/service-env/ai.openclaw.gateway.env prepends that same bin dir to PATH. Upgrading node anywhere else leaves the service on the old runtime. Verify with the service's own argv, not which node.

Rules​

  1. To upgrade the node that services run under, install from a different node install and confirm the resolved path matches the service's absolute argv.
  2. Check npm config get prefix first; the package must land where the LaunchAgent points.
  3. Never install the node package while the only node on PATH comes from that same package.
  4. Verify after: node --version at the service path, then the service's health endpoint.
  5. LaunchAgents that reference a runtime by absolute path (node, python venvs) do not follow PATH changes. Any runtime upgrade must be checked against every such plist.

Node gate for OpenClaw​

Installed OpenClaw version determines the node floor:

  • openclaw 2026.8.1 requires >=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0
  • openclaw 2026.9.4 requires >=24.16.0 <25 || >=26.1.0

node@24.21.0 satisfies both. After the node upgrade, openclaw update --yes --json --timeout 900 completed and restarted its own gateway: CLI 2026.8.1 -> 2026.9.4, health {"ok":true,"status":"live"}.

Contrast with the Hermes gateway​

The OpenClaw updater restarts its own service successfully. The Hermes updater does not when it runs as a descendant of the Hermes gateway; that one leaves ~/.hermes/fleet_restart_pending and requires hermes gateway restart from an outside shell. See lessons/2026-09-16-gateway-restart-blocked-from-inside-gateway.md.