From 0250a010417d8bcf46b2114898ae3ba171997331 Mon Sep 17 00:00:00 2001 From: MickLesk <47820557+MickLesk@users.noreply.github.com> Date: Tue, 1 Sep 2026 10:57:28 +0200 Subject: [PATCH 1/4] Silence npm's update notice so the real error stays visible A failed install reported this: in line 35: exit code 1: npm ci --workspace frontend --- Last 20 lines of log --- npm notice New major version of npm available! 11.19.0 -> 12.0.2 npm notice Changelog: ... npm notice To update run: npm install -g npm@12.0.2 The notice is not the failure. npm prints it at the end of every run, so it occupies the tail of the log -- and the tail is exactly what the error excerpt shows. Three lines of advertising push out the three lines that would have explained why npm ci exited 1. setup_nodejs now turns the notifier off, in the environment for the install itself and in the global npm config so it stays off for whoever works in the container later. 178 install scripts call it. Unrelated but checked while here: npm is pinned in exactly one place, the npm@10.9.8 stepping stone for the Node 22.22.2 self-upgrade regression, and it only fires when npm is exactly 10.9.7. The pnpm@11 and pnpm@10.33.0 pins in five install scripts are pnpm, not npm. --- lib/runtime.func | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/runtime.func b/lib/runtime.func index b558385..6c262b4 100644 --- a/lib/runtime.func +++ b/lib/runtime.func @@ -1317,6 +1317,13 @@ setup_nodejs() { return 127 fi + # npm prints its "New major version available" banner at the end of every + # run, so it lands in the tail of the log -- which is the excerpt shown when + # an install fails. People then read the version notice as the cause and + # never see the lines above it that actually explain the failure. + export NPM_CONFIG_UPDATE_NOTIFIER=false + $STD npm config set update-notifier false --global 2>/dev/null || true + cache_installed_version "nodejs" "$NODE_VERSION" msg_ok "$node_setup_ok_msg" fi From 04bcc977bc1436bf26c7c837de512658aa145488 Mon Sep 17 00:00:00 2001 From: MickLesk <47820557+MickLesk@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:02:22 +0200 Subject: [PATCH 2/4] Say what the npm 10.9.7 block is actually for Checked upstream. nodejs/node#62425 is closed since 2026-05-14: only 22.22.2 shipped npm 10.9.7, and 22.22.3 on 2026-05-13 went back to 10.9.8, as did every 22.x since. A fresh install pulls 22.23.2 and never reaches this branch. It is not dead code though -- it sits outside the install block, so it runs whenever setup_nodejs is called, including on a container that already has Node. What it repairs is a container built during the seven weeks 22.22.2 was current. The comment now says that, with the condition for deleting it. Also installs promise-retry first. The bug is that npm 10.9.7's arborist is missing exactly that module, which is what people upstream found fixed it. The pinned 10.9.8 step stays, since it succeeds where @latest on its own does not. --- lib/runtime.func | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/runtime.func b/lib/runtime.func index 6c262b4..d903909 100644 --- a/lib/runtime.func +++ b/lib/runtime.func @@ -1328,14 +1328,21 @@ setup_nodejs() { msg_ok "$node_setup_ok_msg" fi - # Node 22.22.2 bundles npm 10.9.7 which cannot self-upgrade (nodejs/node#62425) + # Only Node 22.22.2 shipped npm 10.9.7, whose arborist is missing + # promise-retry, so npm cannot upgrade itself (nodejs/node#62425). Fixed + # upstream in 22.22.3 on 2026-05-13, so a fresh install never lands here -- + # this repairs containers built in the seven weeks it was current. Delete it + # once none are left. if [[ "$NODE_VERSION" == "22" && "$(npm -v 2>/dev/null)" == "10.9.7" ]]; then - msg_info "Updating npm (Node 22 regression fix)" + msg_info "Repairing npm 10.9.7 (Node 22.22.2)" + # Supplying the missing module first is what worked for people upstream; + # the pinned step is kept because it succeeds where @latest alone does not. + $STD npm install -g promise-retry 2>/dev/null || true $STD npm install -g npm@10.9.8 2>/dev/null || true if $STD npm install -g npm@latest 2>/dev/null; then - msg_ok "Updated npm ($(npm -v))" + msg_ok "Repaired npm ($(npm -v))" else - msg_warn "npm update failed on Node 22.22.2" + msg_warn "npm could not be repaired - upgrade Node to 22.22.3 or later" fi fi From 9f462066023c1aa696e438655b46138678e3231b Mon Sep 17 00:00:00 2001 From: MickLesk <47820557+MickLesk@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:08:02 +0200 Subject: [PATCH 3/4] Drop the npm version pin The pin was belt and braces on top of a repair that does not need it. The bug is a missing promise-retry in npm 10.9.7's arborist; supply that module and npm upgrades itself normally, which is exactly what the upstream reporters did. Stepping through 10.9.8 first only made sense while the cause was unclear. No npm version is pinned in core now. --- lib/runtime.func | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/runtime.func b/lib/runtime.func index d903909..d2d1a41 100644 --- a/lib/runtime.func +++ b/lib/runtime.func @@ -1335,10 +1335,9 @@ setup_nodejs() { # once none are left. if [[ "$NODE_VERSION" == "22" && "$(npm -v 2>/dev/null)" == "10.9.7" ]]; then msg_info "Repairing npm 10.9.7 (Node 22.22.2)" - # Supplying the missing module first is what worked for people upstream; - # the pinned step is kept because it succeeds where @latest alone does not. + # Supplying the missing module is the whole repair -- once arborist can load + # promise-retry, npm upgrades itself normally. No version pin needed. $STD npm install -g promise-retry 2>/dev/null || true - $STD npm install -g npm@10.9.8 2>/dev/null || true if $STD npm install -g npm@latest 2>/dev/null; then msg_ok "Repaired npm ($(npm -v))" else From f8136ef88925b39ca9695d4e5cd57c49007cc432 Mon Sep 17 00:00:00 2001 From: MickLesk <47820557+MickLesk@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:10:24 +0200 Subject: [PATCH 4/4] Actually update npm, on install and on update Nothing ever ran npm install -g npm. Install took whatever npm NodeSource had bundled with that Node package, and the update path ran apt-get --only-upgrade nodejs, which carries a newer npm only when Node itself moves. So a container sat on months-old npm and npm advertised its own update on every run -- which is how this started. Both paths now bring npm to the newest release. It is a no-op when already current, so the update path needs no separate version check. One thing to weigh: npm@latest means an install picks up a new npm major on the day it lands, and 178 install scripts run through here. NPM_VERSION pins it for any script that a major breaks. --- lib/runtime.func | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/runtime.func b/lib/runtime.func index d2d1a41..3a7f5ef 100644 --- a/lib/runtime.func +++ b/lib/runtime.func @@ -1211,6 +1211,26 @@ _cs_drop_pm_shim() { esac } +# Node ships whatever npm was current when that release was cut, and nothing +# upgraded it afterwards -- which is why installs run months-old npm and npm +# advertises its own update on every run. Both paths bring it to the newest +# release; a no-op when it is already there, so the update path needs no +# separate check. NPM_VERSION pins it if a new major breaks a build. +_setup_npm() { + local target="${NPM_VERSION:-latest}" before after + command -v npm >/dev/null 2>&1 || return 0 + before="$(npm -v 2>/dev/null || echo unknown)" + + if ! $STD npm install -g "npm@${target}" 2>/dev/null; then + msg_warn "npm stayed at ${before}" + return 0 + fi + + after="$(npm -v 2>/dev/null || echo "$before")" + [[ "$after" != "$before" ]] && msg_ok "npm ${before} -> ${after}" + return 0 +} + setup_nodejs() { local NODE_VERSION="${NODE_VERSION:-24}" local NODE_MODULE="${NODE_MODULE:-}" @@ -1249,6 +1269,10 @@ setup_nodejs() { # Upgrade to the latest minor/patch release from NodeSource $STD apt-get install -y --only-upgrade nodejs 2>/dev/null || true + export NPM_CONFIG_UPDATE_NOTIFIER=false + $STD npm config set update-notifier false --global 2>/dev/null || true + _setup_npm + cache_installed_version "nodejs" "$NODE_VERSION" msg_ok "Update Node.js $NODE_VERSION" else @@ -1323,6 +1347,7 @@ setup_nodejs() { # never see the lines above it that actually explain the failure. export NPM_CONFIG_UPDATE_NOTIFIER=false $STD npm config set update-notifier false --global 2>/dev/null || true + _setup_npm cache_installed_version "nodejs" "$NODE_VERSION" msg_ok "$node_setup_ok_msg"