From a972249a2b04e0acf3decf6ff83af3205c99c3d6 Mon Sep 17 00:00:00 2001 From: fred Date: Sun, 16 Aug 2026 15:02:04 -0500 Subject: [PATCH] fleet: put the bootstrapped Node on PANE_PATH (#1256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a host with no system Node, tools/install.sh bootstraps one into ~/.mosaic/node/current/bin and records it in ~/.profile. The fleet unit runs `env -i ... bash --noprofile --norc`, so ~/.profile is never read — that is deliberate — and _build_runtime_bin_prefix did not name the directory itself. Runtime binaries are `#!/usr/bin/env node`, so the pane resolved `mosaic` and then died on `env: 'node': No such file or directory` after an install that reported success. Measured on a greenfield VM. The existing `npm config get prefix` branch cannot cover it: that reports a package prefix (~/.npm-global), never a Node runtime directory. The test case asserts the property rather than the string — it runs the pane for real with a Node-shebang `mosaic` and requires the pane to have executed. A PATH substring check would pass on a fix that put the directory in the wrong position. Red without the launcher change, green with it, rest of the suite unaffected. --- .../tools/fleet/start-agent-session.sh | 9 ++ .../tools/fleet/test-start-agent-session.sh | 83 +++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/packages/mosaic/framework/tools/fleet/start-agent-session.sh b/packages/mosaic/framework/tools/fleet/start-agent-session.sh index 6faffa38..0fced5c1 100755 --- a/packages/mosaic/framework/tools/fleet/start-agent-session.sh +++ b/packages/mosaic/framework/tools/fleet/start-agent-session.sh @@ -268,6 +268,15 @@ esac _build_runtime_bin_prefix() { local candidates=() if [ -n "$MOSAIC_RUNTIME_BIN" ]; then candidates+=("$MOSAIC_RUNTIME_BIN"); fi + # A host with no system Node gets one bootstrapped here by tools/install.sh, which + # records it in ~/.profile. The fleet unit runs `env -i ... bash --noprofile --norc` + # by design, so ~/.profile is never read and the directory has to be named here. + # The npm probe below cannot cover this: it reports a package prefix + # (~/.npm-global), never a Node runtime directory. It is first so the bootstrapped + # runtime wins on a host that has both — that is the one the installer verified. + # Runtime binaries are `#!/usr/bin/env node`, so without this the pane resolves the + # binary and then dies on `env: 'node': No such file or directory`. + candidates+=("$PANE_HOME/.mosaic/node/current/bin") if command -v npm >/dev/null 2>&1; then local npm_prefix npm_prefix=$(npm config get prefix 2>/dev/null) || true diff --git a/packages/mosaic/framework/tools/fleet/test-start-agent-session.sh b/packages/mosaic/framework/tools/fleet/test-start-agent-session.sh index e7d8e94f..182b1ee6 100755 --- a/packages/mosaic/framework/tools/fleet/test-start-agent-session.sh +++ b/packages/mosaic/framework/tools/fleet/test-start-agent-session.sh @@ -348,6 +348,89 @@ for blocked in LD_PRELOAD= BASH_ENV= MOSAIC_UNTRUSTED_SENTINEL=; do echo "$pane_environment" | grep -qF "$blocked" && fail "runtime pane received $blocked" done +# #1256. On a host with no system Node, tools/install.sh bootstraps one into +# ~/.mosaic/node/ and writes that directory to ~/.profile. The fleet unit runs +# `env -i ... bash --noprofile --norc`, so ~/.profile is never read — correctly, by +# design — and _build_runtime_bin_prefix does not list the bootstrap directory. Its +# `npm config get prefix` branch cannot cover the gap either: the installer points +# npm's prefix at ~/.npm-global, so that branch contributes the npm-global directory +# and never the Node one, however it resolves. +# +# The property under test is not "the string is in PATH". It is that the pane can +# EXECUTE a Node-shebang runtime binary — which is what `mosaic` is +# (`#!/usr/bin/env node`) and what actually failed: measured on a greenfield VM as +# `env: 'node': No such file or directory` after a clean install that reported success. +# +# So this case runs the pane for real and requires it to have run. A PATH-substring +# assertion would pass on a fix that put the directory in the wrong position, and it +# would keep passing if the pane later stopped running for some unrelated reason. +: > "$TMUX_CALLS" +HOME_NODE="$ROOT/bootstrap-node/.config/mosaic" +write_generated "$HOME_NODE" "coder-node" +NODE_PANE_HOME="${HOME_NODE%/.config/mosaic}" +NODE_BOOTSTRAP_BIN="$NODE_PANE_HOME/.mosaic/node/current/bin" +mkdir -p "$NODE_BOOTSTRAP_BIN" + +# The bootstrapped runtime. It records that it ran, which is the evidence this case +# turns on: no node reachable from the pane means no marker. +cat > "$NODE_BOOTSTRAP_BIN/node" <<'SHIM' +#!/usr/bin/env bash +set -euo pipefail +env -0 > "${MOSAIC_HOME:?}/fleet/pane-environment" +SHIM +chmod +x "$NODE_BOOTSTRAP_BIN/node" + +# write_generated plants its symlinks under the MOSAIC_HOME it is given; here the +# pane's HOME is the trusted parent, so the pane's view of "installed" is this +# directory instead. `pi` is what #1241 resolves against PANE_PATH; `mosaic` is what +# the pane then executes, and it is a Node script — not a bash script that would run +# anywhere and quietly hide the defect. +mkdir -p "$NODE_PANE_HOME/.npm-global/bin" +ln -sf "$FAKE_BIN/pi" "$NODE_PANE_HOME/.npm-global/bin/pi" +printf '#!/usr/bin/env node\n' > "$NODE_PANE_HOME/.npm-global/bin/mosaic" +chmod +x "$NODE_PANE_HOME/.npm-global/bin/mosaic" + +# The npm branch is modelled ALIVE and still cannot close the gap, which is the +# stronger statement. An earlier draft of this case tried to model npm as absent — +# true on a real bootstrap host, where npm lives only in the Node directory — and it +# refused to run anywhere npm is in the system path, i.e. most machines. It was also +# the weaker claim: it would have proven only that a dead branch supplies nothing. +# +# On a bootstrap host the installer sets npm's prefix to ~/.npm-global. So even with +# `command -v npm` true and the branch executing, `npm config get prefix` yields the +# npm-global directory and never the Node one. The gap does not depend on whether +# that branch runs. +NODE_LAUNCHER_BIN="$ROOT/bootstrap-node-launcher-bin" +mkdir -p "$NODE_LAUNCHER_BIN" +ln -sf "$FAKE_BIN/tmux" "$NODE_LAUNCHER_BIN/tmux" +ln -sf "$FAKE_BIN/npm" "$NODE_LAUNCHER_BIN/npm" + +/usr/bin/env -i \ + "HOME=$NODE_PANE_HOME" \ + "PATH=$NODE_LAUNCHER_BIN:/usr/bin:/bin" \ + "MOSAIC_HOME=$HOME_NODE" \ + "MOSAIC_TEST_TMUX_CALLS=$TMUX_CALLS" \ + "MOSAIC_TEST_HOME=$NODE_PANE_HOME" \ + "MOSAIC_TEST_NPM_PREFIX=$NODE_PANE_HOME/.npm-global" \ + MOSAIC_TEST_FLEET_OWNER=123e4567-e89b-12d3-a456-426614174000 \ + MOSAIC_TEST_EXECUTE_PANE=1 \ + "MOSAIC_TEST_PANE_PID=$$" \ + "$START" coder-node + +[ -f "$HOME_NODE/fleet/pane-environment" ] || \ + fail "pane could not execute a Node-shebang runtime: $NODE_BOOTSTRAP_BIN is absent from PANE_PATH (#1256)" +node_pane_environment=$(tr '\0' '\n' < "$HOME_NODE/fleet/pane-environment") +# Colon-pad and match a whole element. A regex with `(^|:)` after `.*` looks like it +# does this and does not: an anchor cannot match mid-pattern, so it silently requires +# a leading colon and rejects the directory in FIRST position — which is exactly where +# the fix puts it. That produced a failure reading "pane ran but PANE_PATH does not +# carry " against a PATH whose first element was that dir. +node_pane_path=":$(printf '%s\n' "$node_pane_environment" | sed -n 's/^PATH=//p' | head -1):" +case "$node_pane_path" in + *":$NODE_BOOTSTRAP_BIN:"*) ;; + *) fail "pane ran but PANE_PATH does not carry $NODE_BOOTSTRAP_BIN (PATH=$node_pane_path)" ;; +esac + write_interaction_generated() { local home="$1" local agent="$2"