Repository navigation
fix(tui): keep npm-prefix updates on the Node pinned by the receipt - #445
Conversation
The versioned npm-prefix update flow pins every release launcher to one Node executable so the better-sqlite3 binding stays ABI-consistent, and records that executable in install.json. The launcher runtime, however, was taken from process.execPath: on macOS, execPath resolves Homebrew's stable bin/node symlink into a versioned Cellar keg such as Cellar/node/26.7.0/bin/node. After `brew upgrade` removes the old keg, self-updated launchers exec a path that no longer exists and the CLI fails to start with a bare "not found". Reuse the nodeExecutable recorded in the install receipt when it is still present, so launcher activation and the npm/npm validation child processes stay on the pinned executable instead of re-resolving it through process.execPath. Receipts without a usable nodeExecutable keep the previous process.execPath behavior. Versioned launchers now also print an actionable repair hint and exit 127 when their pinned Node is missing, instead of a bare exec failure. Homebrew same-major upgrades keep the ABI stable, so following the formula-managed symlink is safe; a cross-major upgrade surfaces through the existing SQLite validation and installer repair flow. Assisted-by: pi
hetaoBackend
left a comment
There was a problem hiding this comment.
Request changes. The TUI side looks right and CI is green, but on its own this doesn't fix existing Homebrew installs.
Blocking: the live install.sh resolves Node to process.execPath on purpose (L983, "not a version-manager symlink") and writes it to install.json (L958). Homebrew receipts therefore hold Cellar/node/<ver>/bin/node, and preferring the receipt's nodeExecutable still breaks after brew upgrade. With a realistic Cellar receipt I still get exit 127, now with the hint. The E2E test passes only because its receipt stores a symlink, which the installer never writes.
Both of these are needed:
- Installer: for Homebrew only, record
<brew>/opt/<formula>/bin/node. Keep the realpath for fnm, asdf and volta, whose links are per-shell or per-directory. Users who already hit 127 can't startmcodeand can only rerun the installer, which today pins Cellar again. - TUI migration as a complement: if the receipt's realpath is under
<brew>/Cellar/<formula>/<ver>/bin/nodeand<brew>/opt/<formula>/bin/noderesolves to the same file, switch to the opt path, so existing installs heal on their next update.
Prefer opt/<formula>/bin over <brew>/bin/node. brew link --overwrite can repoint bin/node, and createMcodeNpmRuntimeEnvironment prepends the Node directory to PATH, which would put all of /opt/homebrew/bin ahead of npm and the better-sqlite3 build.
Non-blocking:
- After a cross-major Node upgrade, the better-sqlite3 load error suggests
npm install -g … --foreground-scripts(cli/main.ts:213-232), which is wrong for npm-prefix installs. It should point to rerunning the installer. install-source.ts:652uses onlyexistsSync. Please also check that the path is a file and executable (statSync().isFile()+accessSync(X_OK)).- The Windows
.cmd/.ps1launchers have no missing-Node hint. Fine as a follow-up. - Tests to add: a receipt
nodeExecutablethat is missing or relative (falls back toprocess.execPath), a schemaVersion 1 receipt, and a receipt that already holds a Cellar path. - Please link or open an issue with repro steps.
Review follow-up for npm-prefix updates on Homebrew-managed Node. Receipts written by earlier installers record the resolved Cellar keg path, so simply preferring the receipt's nodeExecutable still breaks after `brew upgrade` removes the keg. Canonicalize the recorded path at receipt parse time: when it points into a versioned Cellar/node keg (or a Homebrew-managed bin/node) and the formula's stable opt/<formula>/bin/node resolves to the same executable, use the opt path instead. The opt directory also keeps the update PATH prefix scoped to the Node formula rather than all of <brew>/bin. Unusable entries (missing, relative, non-file, non-executable) fall back to process.execPath as before. Also point the native SQLite startup diagnostic at rerunning the installer for npm-prefix installs instead of an npm-global reinstall command. Assisted-by: pi
|
Thanks for the thorough review — all blocking points addressed in 386b7ce. Blocking
Non-blocking
Validation on 386b7ce (local macOS, Node 22.22): |
|
@hetaoBackend PTAL |
|
Thanks, 386b7ce addresses the review: the TUI migration from Cellar to Two things before approval:
Once the installer follow-up is tracked and the nit is fixed, I'm happy to approve. |
|
Installer follow-up is now tracked in #457. Still waiting on the small |
Review follow-up: the formula branch of HOMEBREW_MANAGED_NODE_PATH was missing the opt/ literal, so it matched the nonexistent <brew>/node/bin/node while real opt/<formula>/bin/node receipts never matched. opt receipts are still returned unchanged either way, but the pattern now recognizes them explicitly and the escaped linuxbrew segment stops matching lookalike prefixes. Adds a pass-through test for opt receipts. Assisted-by: pi
|
Nit fixed in 299822a: the formula branch now matches Installer side: thanks for opening #457 — PR description now links it. One clarification on its Context section: the next installer release records the formula-managed |
hetaoBackend
left a comment
There was a problem hiding this comment.
Thanks — the opt/ regex fix and the pass-through test look good, and the installer follow-up is tracked in #457. LGTM.
Change
Fixes the TUI side of #446 (CLI fails to start after
brew upgraderemoves the pinned Homebrew Node keg).Installer side: tracked in #457. The upcoming installer release records the formula-managed
bin/nodesymlink instead of the Cellar keg, so reinstalling afterbrew upgraderestores a working launcher directly; recording theopt/<formula>/bin/nodepath per #457 is the remaining refinement, and existing installs converge on the opt path through the in-app update migration added here.On macOS, a Homebrew-managed Node resolves through the stable
bin/nodesymlink into a versioned Cellar keg such asCellar/node/26.7.0/bin/node. The npm-prefix update flow pinned release launchers toprocess.execPath, which is that versioned keg path. Afterbrew upgraderemoves the old keg, the pinned launcher execs a path that no longer exists and the CLI fails to start with a bare "not found" (exit 127), with no self-healing path.npm-prefix updates now reuse the
nodeExecutablerecorded ininstall.jsonwhen it still exists, so launcher activation, the npm child process, and SQLite validation stay on the pinned executable instead of re-resolving it throughprocess.execPath. Receipts without a usablenodeExecutablekeep the previous behavior.Versioned launchers print an actionable repair hint (re-run the installer) and exit 127 when their pinned Node is missing, instead of a bare exec failure.
Same-major Homebrew upgrades keep the Node ABI stable, so following the formula-managed symlink is safe; cross-major drift still surfaces through the existing SQLite validation and installer repair flow.
PR labels:
bug,cli,tui386b7ce (review follow-up):
update-runtime.test.ts9/9 passed, adding a Cellar-receipt E2E that heals to the Homebrewopt/<formula>/bin/nodepath (receipt + launcher assertions, no/Cellar/remains), a missing-receipt-Node fallback E2E, and unit coverage for relative / missing / directorynodeExecutableplus a schemaVersion 1 receipt;pnpm run typecheckand ESLint on changed files passed.Checks run and results (revision
129e3e8, local macOS, Node 22.22):node scripts/run-vitest-suite.mjs capability/ focused files: newpackages/tui/test/unit/update-runtime.test.ts6/6 passed, including an end-to-end case that pins a Homebrew-style symlink from the receipt, verifies the activated launcher and new receipt keep the symlink (notprocess.execPath), and checks the exit-127 repair hint after the symlink is removed; siblingupdate-application.test.ts/update-service.test.ts87 passed, 3 skipped.pnpm run typecheck,pnpm run build,pnpm run lint:tui,pnpm run check:source,pnpm run check:tsconfig: passed.release/public-source.jsonregenerated vianode scripts/source-inventory.mjs --writeafter adding the test file; new test declared intest/vitest-suites.json(capability gate).Performance: basic.
NOT RUN, platform limitations and live-service boundaries:
pnpm verifycapability gate: 17 test files inlocal-runtime/local-runtime-v2/ repo-level suites fail on this machine, reproduced identically on cleanmainwith this branch stashed (pre-existing local-environment failures, unrelated to the changed files). Relying on CI for the full matrix.brew upgradeexercised on a physical Homebrew Node install; the keg-removal scenario is covered by the simulated fixture. No live-service or Windows-specific validation run locally.Publication and contribution checks
release/public-source.json; new tests are declared intest/vitest-suites.jsonwhere applicable.Maintainer handoff
Publication scope or license changes (if any): none.
Shared-source port: complete for the TUI update flow covered here; installer-side distribution changes ride the regular installer release process.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.