newest_matching_file() piped `ls -1t` into `head -1`. Under `set -o pipefail`
head closes the pipe after the first line, ls dies on SIGPIPE, and the function
returns 141 having printed nothing. Its callers assign it at top level under
`set -e`, so that 141 aborts the install.
It takes roughly 1600 matching names to fill the pipe buffer, which is why this
has sat unnoticed: with two or three files the old code is correct. Measured on
origin/next with 5001 matches, the function returns 141 and prints nothing; with
this change it returns rc=0 and the right filename.
Two of the four callers are the "find the newest .mosaic-bak-* backup" lookup,
which is the path a restore leans on.
Reading the listing into an array through process substitution has no pipeline,
so there is nothing for pipefail to catch. This also clears the one remaining
violation `scripts/pipefail-early-exit.test.mjs` reports against tools/install.sh
-- that test lives on main, not on next, so it starts failing the moment main is
merged into next for the 0.0.50 integration.
tools/install-newest-matching-file.test.sh pins it, including the large-population
case that is the whole point. Red on origin/next (rc=141), green here.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WYgWocp36goy8hj2ui6ps1
The installer's promise is that one command turns a bare host into a working
one, but Node was carved out of that: it was checked as a prerequisite and the
run died on a greenfield host. That made the documented one-command install a
two-command install whose first command always failed.
It now installs a user-local Node under ~/.mosaic/node when the system Node is
missing or too old, from the official nodejs.org tarballs, verified against
SHASUMS256.txt. User-local rather than apt/dnf/brew: no root, one code path on
every distro, and it works on an immutable host. A system Node that is already
new enough is preferred and left untouched. --no-node-install (or
MOSAIC_NO_NODE_INSTALL=1) keeps the old refuse-and-explain behaviour, and
neither --check nor --uninstall provisions anything.
PATH now lands in the login profile as well as the interactive rc. Writing only
~/.bashrc looked right interactively and was invisible to every way an agent
seat actually starts -- bash -lc, ssh host cmd, a systemd unit -- because
Debian's .bashrc returns early when non-interactive.
Verified end to end on mosaic-sbx-dev rolled back to its greenfield snapshot:
red on origin/next (rc=1, "Required command not found: node"), green with this
change (Node v22.23.2 fetched and verified, CLI 0.0.50-next.2413 installed), and
a fresh `bash -lc` finds both. tools/install-node-provisioning.test.sh pins the
behaviour offline against a file:// dist fixture, including the refusals and the
checksum gate.
The next-lane test's Node 20 case moves to --no-node-install: the >= 22 gate must
still fire before anything is installed, but refusing is no longer the outcome
when provisioning is allowed.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WYgWocp36goy8hj2ui6ps1
--next now prefers a fast npm @next install (CLI + gateway from the Gitea registry) and falls back to source build at next if the dist-tag is unavailable. Registry lane gated to non-dev, non-explicit-ref next installs; CLI/gateway prerelease versions must share a pipeline suffix. Adds tools/install-next-lane.test.sh (wired into CI). PR-event CI 1635 fully green + review-of-record APPROVE (functional install test, head 2fd7cfc3).
Co-Authored-By: Claude Opus 4.8 <[email protected]>