openclaw - 💡(How to fix) Fix Auto-update fails on npm 11+/pnpm installs due to hardlink rejection in swap step

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

The background auto-update mechanism fails on every attempt for installs where the package tree contains hardlinks, which is the default behaviour of npm v9+ and pnpm. The result is a noisy 2-hour retry loop where every attempt rolls back cleanly (no actual data loss) but version never advances.

Error Message

Update Result: ERROR | Log noise | One attempt → success | Every ~2hr → ERROR retry |

Root Cause

src/infra/package-update-steps.ts calls movePathWithCopyFallback with sourceHardlinks: "reject" at lines 295, 302, 334 (and similarly in src/infra/install-package-dir.ts at 216, 303, 318). The guard assertNoHardlinkedSourceFiles in src/infra/replace-file.ts:36-62 recursively walks the source tree and throws on any file with nlink > 1.

Both npm 11+ and pnpm hardlink from their content-addressed caches by default (npm via its cacache layer, pnpm via store). A typical openclaw global install therefore has hardlinks in node_modules/.bin/* and bundled binaries like node_modules/@esbuild/*/bin/esbuild.

Verified locally:

$ find /opt/homebrew/lib/node_modules/openclaw -type f -links +1 | head
/opt/homebrew/lib/node_modules/openclaw/node_modules/esbuild/bin/esbuild
/opt/homebrew/lib/node_modules/openclaw/node_modules/@esbuild/darwin-arm64/bin/esbuild

The actual underlying move (movePathWithCopyFallbackBase from @openclaw/fs-safe/atomic) has no problem with hardlinks — only the upstream assertion is fatal.

Fix Action

Fix / Workaround

Workaround (for affected users)

Code Example

Update Result: ERROR
  Root: /opt/homebrew/lib/node_modules/openclaw
  Reason: global install swap
  Before: 2026.5.12
  After:  2026.5.12

---

$ find /opt/homebrew/lib/node_modules/openclaw -type f -links +1 | head
/opt/homebrew/lib/node_modules/openclaw/node_modules/esbuild/bin/esbuild
/opt/homebrew/lib/node_modules/openclaw/node_modules/@esbuild/darwin-arm64/bin/esbuild
RAW_BUFFERClick to expand / collapse

Summary

The background auto-update mechanism fails on every attempt for installs where the package tree contains hardlinks, which is the default behaviour of npm v9+ and pnpm. The result is a noisy 2-hour retry loop where every attempt rolls back cleanly (no actual data loss) but version never advances.

Environment

  • macOS 15.x (Darwin 25.x)
  • Node.js v24.6.0 (Homebrew)
  • npm v11.5.1
  • openclaw 2026.5.12 → tried to update to 2026.5.20
  • Install via npm install -g openclaw at /opt/homebrew/lib/node_modules/openclaw/

Repro

  1. Install openclaw globally via npm v11+ (npm uses hardlinks from cache by default)
  2. Enable update.auto.enabled: true in ~/.openclaw/openclaw.json
  3. Wait for a newer version to be released
  4. Auto-update fires; observe [gateway] auto-update attempt failed in gateway log every ~2 hours

openclaw update shows:

Update Result: ERROR
  Root: /opt/homebrew/lib/node_modules/openclaw
  Reason: global install swap
  Before: 2026.5.12
  After:  2026.5.12

Root cause

src/infra/package-update-steps.ts calls movePathWithCopyFallback with sourceHardlinks: "reject" at lines 295, 302, 334 (and similarly in src/infra/install-package-dir.ts at 216, 303, 318). The guard assertNoHardlinkedSourceFiles in src/infra/replace-file.ts:36-62 recursively walks the source tree and throws on any file with nlink > 1.

Both npm 11+ and pnpm hardlink from their content-addressed caches by default (npm via its cacache layer, pnpm via store). A typical openclaw global install therefore has hardlinks in node_modules/.bin/* and bundled binaries like node_modules/@esbuild/*/bin/esbuild.

Verified locally:

$ find /opt/homebrew/lib/node_modules/openclaw -type f -links +1 | head
/opt/homebrew/lib/node_modules/openclaw/node_modules/esbuild/bin/esbuild
/opt/homebrew/lib/node_modules/openclaw/node_modules/@esbuild/darwin-arm64/bin/esbuild

The actual underlying move (movePathWithCopyFallbackBase from @openclaw/fs-safe/atomic) has no problem with hardlinks — only the upstream assertion is fatal.

Expected vs actual

ExpectedActual
Same-version reinstallUpdate applies cleanlyThrows on hardlink, rolls back
Before vs After2026.5.20Both 2026.5.12
Log noiseOne attempt → successEvery ~2hr → ERROR retry

Suggested direction

The cleanest fix is probably to flip sourceHardlinks from "reject" to "allow" at the 6 call sites in package-update-steps.ts and install-package-dir.ts, since the underlying move handles hardlinks correctly. If hardlinks ever did cause a real problem the test suite would catch it (and the docs/option could grow a third "materialize" mode that copies-then-unlinks).

Happy to send a PR if the direction is agreed.

Impact

  • Anyone who installed via npm v9+ or any pnpm version
  • macOS users especially (Homebrew Node ships recent npm)
  • Affects auto-update silently — version stays pinned, users may not notice until much later

Workaround (for affected users)

  1. Set update.auto.enabled: false in ~/.openclaw/openclaw.json
  2. Manually upgrade: npm install -g openclaw@latest && openclaw gateway restart

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix Auto-update fails on npm 11+/pnpm installs due to hardlink rejection in swap step