fix(update): keep self-updates on the running install's global root#101228
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 7, 2026, 12:03 AM ET / 04:03 UTC. Summary PR surface: Source +38, Tests +375, Other +23. Total +436 across 7 files. Reproducibility: yes. Current main's resolver can still fall through to PATH's probed npm root, and the PR includes live mixed nvm/Homebrew proof showing the fixed path updates the running package tree. Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review detailsBest possible solution: Refresh to a clean update-focused merge result, rerun exact-head checks, then land the resolver fix if maintainers accept the npm target-precedence compatibility change. Do we have a high-confidence way to reproduce the issue? Yes. Current main's resolver can still fall through to PATH's probed npm root, and the PR includes live mixed nvm/Homebrew proof showing the fixed path updates the running package tree. Is this the best way to solve the issue? Yes, after branch refresh. The shared global install target resolver is the right layer because CLI and gateway update flows consume that target before staging, verification, swap, and post-update doctor work. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against dc5d4138f9ae. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +38, Tests +375, Other +23. Total +436 across 7 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (3 earlier review cycles)
|
|
Maintainer fixup and verification completed on the final reviewed patch, merged from exact head The final refactor keeps the proven fix and narrows its policy boundary: a recognized running npm prefix now wins over a divergent PATH probe ( Proof:
Known gap: the contributor's live update proof is macOS; the final exact-head hosted proof is Linux/Node 24. The final fixup does not change the live-proven divergent-running-root branch; it removes unrelated probe policy and adds the local-root guard regression ( |
f17f77b to
bf3128a
Compare
Package self-updates resolved their install target from `npm root -g`
even when the running install's package root was already known. In an
environment where PATH's npm does not belong to the running install's
Node tree (a launchd/systemd service PATH, or mixed Homebrew + nvm
setups), the probe points at a different global root — or derives a
brand-new one — so the update installs there, reports success, and
leaves the running install stale.
Observed in the wild: a gateway-managed `openclaw update --yes` on
2026.5.28 ran nvm's npm under Homebrew's node, npm derived its default
prefix from the realpathed execPath, and the update installed a full
copy into /opt/homebrew/Cellar/node/26.3.1/lib/node_modules/openclaw
("before": null — a tree that had never existed) while the running nvm
install stayed on the old version until a manual shell update.
resolveGlobalInstallTarget now prefers the package root's own global
root for npm targets; the `npm root -g` probe only decides the target
when no package root is known, and an ephemeral per-Node probe result
(Homebrew Cellar, nvm/n/asdf/volta version dirs — the same set
openclaw#93650 filters npm commands against) is never adopted.
The running-root fallback missed scoped installs because inferGlobalRootFromPackageRoot only accepted an immediate node_modules parent. Hop over an @scope segment so node_modules/@scope/name resolves to the same global root. Regression test: scoped package root with an ephemeral probe now falls back to the running root.
95d2960 to
735c262
Compare
|
Land-ready proof for exact head
Known proof gaps: none for the changed self-update root-selection surface. |
|
Merged via squash.
|
…penclaw#101228) * fix(update): keep self-updates on the running install's global root Package self-updates resolved their install target from `npm root -g` even when the running install's package root was already known. In an environment where PATH's npm does not belong to the running install's Node tree (a launchd/systemd service PATH, or mixed Homebrew + nvm setups), the probe points at a different global root — or derives a brand-new one — so the update installs there, reports success, and leaves the running install stale. Observed in the wild: a gateway-managed `openclaw update --yes` on 2026.5.28 ran nvm's npm under Homebrew's node, npm derived its default prefix from the realpathed execPath, and the update installed a full copy into /opt/homebrew/Cellar/node/26.3.1/lib/node_modules/openclaw ("before": null — a tree that had never existed) while the running nvm install stayed on the old version until a manual shell update. resolveGlobalInstallTarget now prefers the package root's own global root for npm targets; the `npm root -g` probe only decides the target when no package root is known, and an ephemeral per-Node probe result (Homebrew Cellar, nvm/n/asdf/volta version dirs — the same set openclaw#93650 filters npm commands against) is never adopted. * fix(update): infer global roots for scoped package roots The running-root fallback missed scoped installs because inferGlobalRootFromPackageRoot only accepted an immediate node_modules parent. Hop over an @scope segment so node_modules/@scope/name resolves to the same global root. Regression test: scoped package root with an ephemeral probe now falls back to the running root. * fix(update): scope running-root selection * docs(changelog): note npm self-update root fix * chore(changelog): defer release note --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
…penclaw#101228) * fix(update): keep self-updates on the running install's global root Package self-updates resolved their install target from `npm root -g` even when the running install's package root was already known. In an environment where PATH's npm does not belong to the running install's Node tree (a launchd/systemd service PATH, or mixed Homebrew + nvm setups), the probe points at a different global root — or derives a brand-new one — so the update installs there, reports success, and leaves the running install stale. Observed in the wild: a gateway-managed `openclaw update --yes` on 2026.5.28 ran nvm's npm under Homebrew's node, npm derived its default prefix from the realpathed execPath, and the update installed a full copy into /opt/homebrew/Cellar/node/26.3.1/lib/node_modules/openclaw ("before": null — a tree that had never existed) while the running nvm install stayed on the old version until a manual shell update. resolveGlobalInstallTarget now prefers the package root's own global root for npm targets; the `npm root -g` probe only decides the target when no package root is known, and an ephemeral per-Node probe result (Homebrew Cellar, nvm/n/asdf/volta version dirs — the same set openclaw#93650 filters npm commands against) is never adopted. * fix(update): infer global roots for scoped package roots The running-root fallback missed scoped installs because inferGlobalRootFromPackageRoot only accepted an immediate node_modules parent. Hop over an @scope segment so node_modules/@scope/name resolves to the same global root. Regression test: scoped package root with an ephemeral probe now falls back to the running root. * fix(update): scope running-root selection * docs(changelog): note npm self-update root fix * chore(changelog): defer release note --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
…penclaw#101228) * fix(update): keep self-updates on the running install's global root Package self-updates resolved their install target from `npm root -g` even when the running install's package root was already known. In an environment where PATH's npm does not belong to the running install's Node tree (a launchd/systemd service PATH, or mixed Homebrew + nvm setups), the probe points at a different global root — or derives a brand-new one — so the update installs there, reports success, and leaves the running install stale. Observed in the wild: a gateway-managed `openclaw update --yes` on 2026.5.28 ran nvm's npm under Homebrew's node, npm derived its default prefix from the realpathed execPath, and the update installed a full copy into /opt/homebrew/Cellar/node/26.3.1/lib/node_modules/openclaw ("before": null — a tree that had never existed) while the running nvm install stayed on the old version until a manual shell update. resolveGlobalInstallTarget now prefers the package root's own global root for npm targets; the `npm root -g` probe only decides the target when no package root is known, and an ephemeral per-Node probe result (Homebrew Cellar, nvm/n/asdf/volta version dirs — the same set openclaw#93650 filters npm commands against) is never adopted. * fix(update): infer global roots for scoped package roots The running-root fallback missed scoped installs because inferGlobalRootFromPackageRoot only accepted an immediate node_modules parent. Hop over an @scope segment so node_modules/@scope/name resolves to the same global root. Regression test: scoped package root with an ephemeral probe now falls back to the running root. * fix(update): scope running-root selection * docs(changelog): note npm self-update root fix * chore(changelog): defer release note --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
What Problem This Solves
openclaw updatecan run from one npm global install while thenpmfound onPATHderivesnpm root -gfrom another Node installation. In mixed Homebrew plus nvm/asdf/Volta setups, the updater could install successfully into that second tree and leave the running OpenClaw copy unchanged.This PR keeps npm self-updates on the global prefix that owns the running OpenClaw package. It also handles scoped package roots and retains the package-manager probe when the package root is not a valid global npm layout.
Why This Change Was Made
The final implementation resolves the target at the shared
resolveGlobalInstallTargetboundary:<prefix>/lib/node_modules/openclawselects thatnode_modulesroot even when PATH's probe diverges.<prefix>/lib/node_modules/@scope/nameresolves to the same owning root.npm root -g.This is narrower than trusting any path shaped like
node_modules; only recognized npm global-prefix layouts receive the preference.User Impact
Self-update changes the OpenClaw installation that actually launched the command instead of creating or refreshing a stale second copy. No config, CLI, or migration surface changes.
Evidence
tbx_01kwxa3ffx5t6s471n157f2dfv: 277 focused tests passed acrossupdate-global,update-runner, and update CLI coverage.pnpm build: passed.pnpm check:changed: passed.Closes the mixed-Node self-update targeting failure described in this PR. Thanks @buddyh for the report and original fix.