Summary
On a user-writable global openclaw install (macOS + Homebrew-provided Node, no sudo required because Homebrew's lib/node_modules/ is user-owned), openclaw update fails after the npm install -g step completes successfully — the post-install bundled plugin runtime-deps copy phase creates a transient staging directory whose path the same install flow's own safety check then rejects as an "Unsafe package dist path".
aaajiao@aaajiao-M4-Max-16 ~> openclaw update
Updating OpenClaw...
│
◇ ✓ Updating via package manager (15.52s)
Error: Unsafe package dist path: dist/extensions/amazon-bedrock/.openclaw-runtime-deps-copy-KZmXaz/node_modules/.bin/fxparser
The leading . on the staging directory name (.openclaw-runtime-deps-copy-<random>) is what makes the check bail. The staging dir is self-cleaned on error (confirmed by find returning no such directory after the failure), which makes this purely self-induced: openclaw's own post-install creates a path that openclaw's own subsequent safety check immediately rejects.
Behavior
npm install -g openclaw@<version> completes (CLI binary + dist/ tree are now at the new version — openclaw --version reports the new number).
openclaw update's post-install phase walks bundled plugins and prepares their runtime deps. For each bundled plugin with runtime deps it:
- Creates
dist/extensions/<plugin>/.openclaw-runtime-deps-copy-<6-char-random>/
- Populates it with the plugin's runtime deps (including npm-created bin symlinks such as
.bin/fxparser for fast-xml-parser)
- Intends to atomically rename/move it into
dist/extensions/<plugin>/node_modules/
- Before the rename completes, the safety check walks
dist/extensions/ and flags the .openclaw-runtime-deps-copy-* path as unsafe (.-prefixed names are rejected).
- Install fails with the error above. Staging directory is cleaned up as part of the error path.
- Result: the
dist/ tree is at the new version but the bundled plugin whose staging collided (e.g. amazon-bedrock) may have partially-populated node_modules/ or the previous version's node_modules/.
Subsequent openclaw update invocations report:
Update Result: SKIPPED
Root: /opt/homebrew/lib/node_modules/openclaw
Reason: already-current
Before: 2026.4.23
After: 2026.4.23
Total time: 0ms
because step 1 already brought the CLI version up, so the next update has nothing to do and never re-attempts the post-install phase. The user is left with an install that appears current but skipped the runtime-deps finalization for the colliding plugin. The problem re-triggers on the next real version bump.
Evidence that the staging dir is self-created and self-cleaned
After the error fires, nothing is left on disk under the install tree matching the staging name:
$ find /opt/homebrew/lib/node_modules/openclaw/dist/extensions -maxdepth 2 -type d -name ".openclaw-runtime-deps-copy-*"
# (no output)
$ find /opt/homebrew/lib/node_modules -type d -name ".openclaw-runtime-deps-copy-*"
# (no output)
$ find /tmp /var/folders ~/.openclaw -type d -name ".openclaw-runtime-deps-copy-*" 2>/dev/null
# (no output)
And dist/extensions/amazon-bedrock/ already has its node_modules/ (sibling to the source files), so the install layout on user-writable installs is runtime deps live in-tree under dist/extensions/<plugin>/node_modules/ — not in ~/.openclaw/plugin-runtime-deps/ like on root-owned global installs:
$ ls /opt/homebrew/lib/node_modules/openclaw/dist/extensions/amazon-bedrock/
api.js
config-compat.js
discovery.js
embedding-provider.js
index.js
memory-embedding-adapter.js
node_modules
openclaw.plugin.json
package.json
register.sync.runtime.js
setup-api.js
$ ls ~/.openclaw/plugin-runtime-deps/
(does not exist — this path is only used by root-owned global installs)
So the .openclaw-runtime-deps-copy-<random>/ directory is a transient atomicity-copy target internal to the post-install, not a leftover artifact or persistent staging.
Reproduction
- Clean macOS arm64 host with Homebrew-managed Node (Homebrew's
/opt/homebrew/lib/node_modules/ is user-owned, so global npm install -g openclaw does not require sudo)
- Install openclaw globally:
brew install node then npm install -g openclaw@<prev-version> (any version before 2026.4.23)
- Run
openclaw update to bump to 2026.4.23
Expected: CLI + all bundled plugin runtime deps updated cleanly.
Actual: CLI updates, but the post-install phase aborts with Error: Unsafe package dist path: dist/extensions/amazon-bedrock/.openclaw-runtime-deps-copy-<random>/node_modules/.bin/fxparser.
Subsequent openclaw update runs report already-current and skip, masking the partial state.
Suggested upstream fixes
Three non-exclusive options:
- Change staging name to not start with
. — use e.g. _staging-runtime-deps-<random>/ or a single well-known bucket like dist/extensions/<plugin>/_staging/<random>/. Keeps staging in-tree for locality but avoids the "unsafe dotfile-prefixed dir" rule.
- Move staging out of
dist/ entirely — create under $TMPDIR (mkdtemp()), populate there, then rename() into dist/extensions/<plugin>/node_modules/. If the target is on the same filesystem as $TMPDIR, rename stays atomic. Benefits: safety check never has to know about staging existence.
- Whitelist the openclaw-owned staging prefix in the safety check — if the intent of rejecting
.-prefixed names in dist/ is to block arbitrary foreign hidden directories, explicitly allow the single pattern ^\.openclaw-runtime-deps-copy-[A-Za-z0-9]+$ that openclaw's own post-install creates.
Option (2) is the most robust (staging physically cannot collide with safety check because it's not in dist/). Option (3) is the smallest diff.
Environment
- OpenClaw:
2026.4.23
- Node: Homebrew-installed (Homebrew
node formula as of 2026-04-24)
- npm: bundled with Homebrew node
- OS: macOS, arm64 (Apple Silicon)
- Install path:
/opt/homebrew/lib/node_modules/openclaw (user-owned via Homebrew Node)
openclaw binary: /opt/homebrew/bin/openclaw → symlink to ../lib/node_modules/openclaw/openclaw.mjs
- Install method:
npm install -g openclaw@<version> (no sudo; Homebrew's lib tree is user-writable)
Why this may be less visible in other install topologies
- Root-owned global installs (Linux +
sudo npm install -g, typical Docker image): post-install instead writes runtime deps into ~/.openclaw/plugin-runtime-deps/openclaw-<ver>-<hash>/ (external to the root-owned install tree), so dist/extensions/<plugin>/ is never touched by the copy phase and the safety check never sees the staging dir there. Confirmed by checking a Linux VM install on 2026.4.23: dist/extensions/<plugin>/ has no node_modules/, and ~/.openclaw/plugin-runtime-deps/openclaw-2026.4.23-<hash>/ holds the full dep tree.
- User-writable global installs (Homebrew Node on macOS,
nvm-managed Node on Linux, pnpm install -g in user space): post-install writes in-tree under dist/extensions/<plugin>/node_modules/, which is where the staging collision happens.
So this bug is specifically visible on user-writable installs. Anyone using Homebrew Node on macOS or nvm/pnpm on Linux will hit it on every real version bump.
Related
Summary
On a user-writable global
openclawinstall (macOS + Homebrew-provided Node, no sudo required because Homebrew'slib/node_modules/is user-owned),openclaw updatefails after thenpm install -gstep completes successfully — the post-install bundled plugin runtime-deps copy phase creates a transient staging directory whose path the same install flow's own safety check then rejects as an "Unsafe package dist path".The leading
.on the staging directory name (.openclaw-runtime-deps-copy-<random>) is what makes the check bail. The staging dir is self-cleaned on error (confirmed byfindreturning no such directory after the failure), which makes this purely self-induced: openclaw's own post-install creates a path that openclaw's own subsequent safety check immediately rejects.Behavior
npm install -g openclaw@<version>completes (CLI binary +dist/tree are now at the new version —openclaw --versionreports the new number).openclaw update's post-install phase walks bundled plugins and prepares their runtime deps. For each bundled plugin with runtime deps it:dist/extensions/<plugin>/.openclaw-runtime-deps-copy-<6-char-random>/.bin/fxparserforfast-xml-parser)dist/extensions/<plugin>/node_modules/dist/extensions/and flags the.openclaw-runtime-deps-copy-*path as unsafe (.-prefixed names are rejected).dist/tree is at the new version but the bundled plugin whose staging collided (e.g.amazon-bedrock) may have partially-populatednode_modules/or the previous version'snode_modules/.Subsequent
openclaw updateinvocations report:because step 1 already brought the CLI version up, so the next update has nothing to do and never re-attempts the post-install phase. The user is left with an install that appears current but skipped the runtime-deps finalization for the colliding plugin. The problem re-triggers on the next real version bump.
Evidence that the staging dir is self-created and self-cleaned
After the error fires, nothing is left on disk under the install tree matching the staging name:
And
dist/extensions/amazon-bedrock/already has itsnode_modules/(sibling to the source files), so the install layout on user-writable installs is runtime deps live in-tree underdist/extensions/<plugin>/node_modules/— not in~/.openclaw/plugin-runtime-deps/like on root-owned global installs:So the
.openclaw-runtime-deps-copy-<random>/directory is a transient atomicity-copy target internal to the post-install, not a leftover artifact or persistent staging.Reproduction
/opt/homebrew/lib/node_modules/is user-owned, so globalnpm install -g openclawdoes not require sudo)brew install nodethennpm install -g openclaw@<prev-version>(any version before 2026.4.23)openclaw updateto bump to 2026.4.23Expected: CLI + all bundled plugin runtime deps updated cleanly.
Actual: CLI updates, but the post-install phase aborts with
Error: Unsafe package dist path: dist/extensions/amazon-bedrock/.openclaw-runtime-deps-copy-<random>/node_modules/.bin/fxparser.Subsequent
openclaw updateruns report already-current and skip, masking the partial state.Suggested upstream fixes
Three non-exclusive options:
.— use e.g._staging-runtime-deps-<random>/or a single well-known bucket likedist/extensions/<plugin>/_staging/<random>/. Keeps staging in-tree for locality but avoids the "unsafe dotfile-prefixed dir" rule.dist/entirely — create under$TMPDIR(mkdtemp()), populate there, thenrename()intodist/extensions/<plugin>/node_modules/. If the target is on the same filesystem as$TMPDIR, rename stays atomic. Benefits: safety check never has to know about staging existence..-prefixed names indist/is to block arbitrary foreign hidden directories, explicitly allow the single pattern^\.openclaw-runtime-deps-copy-[A-Za-z0-9]+$that openclaw's own post-install creates.Option (2) is the most robust (staging physically cannot collide with safety check because it's not in
dist/). Option (3) is the smallest diff.Environment
2026.4.23nodeformula as of 2026-04-24)/opt/homebrew/lib/node_modules/openclaw(user-owned via Homebrew Node)openclawbinary:/opt/homebrew/bin/openclaw→ symlink to../lib/node_modules/openclaw/openclaw.mjsnpm install -g openclaw@<version>(no sudo; Homebrew's lib tree is user-writable)Why this may be less visible in other install topologies
sudo npm install -g, typical Docker image): post-install instead writes runtime deps into~/.openclaw/plugin-runtime-deps/openclaw-<ver>-<hash>/(external to the root-owned install tree), sodist/extensions/<plugin>/is never touched by the copy phase and the safety check never sees the staging dir there. Confirmed by checking a Linux VM install on 2026.4.23:dist/extensions/<plugin>/has nonode_modules/, and~/.openclaw/plugin-runtime-deps/openclaw-2026.4.23-<hash>/holds the full dep tree.nvm-managed Node on Linux,pnpm install -gin user space): post-install writes in-tree underdist/extensions/<plugin>/node_modules/, which is where the staging collision happens.So this bug is specifically visible on user-writable installs. Anyone using Homebrew Node on macOS or nvm/pnpm on Linux will hit it on every real version bump.
Related
sudo --preserve-env=HOME openclaw doctor --fixwrites npm cache as root into user's~/.npm/, breaking later non-root calls). Same overall "post-install runtime-deps is fragile" theme, independent root causes.openclaw plugins install, different code path but same-ish subsystem).