From abb84c4cdfe5085d515d9f03dbff9beaac2fc6a3 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sat, 1 Aug 2026 21:22:00 -0500 Subject: [PATCH] fix(install): upgrade npm on the managed-Node reuse path too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _nb_ensure_bundled_npm_range ran only at the tail of _nb_install_bundled_node, so it fired just after a tarball was unpacked. ensure_node's reuse rung returns before reaching it, leaving an existing managed tree on whatever npm its Node major bundled. That strands a real install: the upgrade is best-effort (`|| true`), so one offline run leaves an at-target Node 26 tree carrying npm 11.17.0 — below the root package.json's `engines.npm` floor of >=12, fatal under .npmrc's engine-strict. Heal does not cover it either; the tree is at the target major and every binary passes --version, so _nb_managed_node_needs_heal correctly reports it healthy. Re-running the installer, the documented recovery, never repaired it. install.ps1 already had this right: Update-ManagedNpm is called from both branches that yield a managed tree, including the reuse path. This is the POSIX side of that same call site. Reproduced on a seeded node-26.5.1/npm-11.17.0 tree: before, ensure_node left npm at 11.17.0 and `npm ci` died with EBADENGINE; after, it upgrades to 12.0.2 and `npm ci` installs 208 packages. An already-in-range tree costs one --version probe (~0.13s), and the system-node path is unchanged. Co-authored-by: ethernet8023 --- scripts/lib/node-bootstrap.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/lib/node-bootstrap.sh b/scripts/lib/node-bootstrap.sh index fd48374b4802..1c6831015641 100644 --- a/scripts/lib/node-bootstrap.sh +++ b/scripts/lib/node-bootstrap.sh @@ -408,6 +408,13 @@ ensure_node() { if _nb_have_modern_node; then _nb_ok "Node $(node --version) found (Hermes-managed)" HERMES_NODE_AVAILABLE=true + # A tree from an older install still carries that Node major's + # bundled npm, and the upgrade in _nb_install_bundled_node is + # best-effort — one offline install leaves an at-target tree + # stranded below engines.npm forever, since heal only fires for a + # *broken* tree. Mirrors Update-ManagedNpm's reuse-path call in + # install.ps1. No-ops on a probe when the npm is already in range. + _nb_ensure_bundled_npm_range || true return 0 fi fi