fix(scripts): start-local.ps1 asks nvm for a version it cannot have, then blames the wrong thing #198

Closed
opened 2026-08-27 11:36:23 -05:00 by bermudalamb · 0 comments
Owner

.\scripts\start-local.ps1 fails on a machine that has everything it needs:

==> Switching Node to latest (this project needs Node 20 or newer)
    node v18.16.1
Exception: scripts\NodeVersion.ps1:109
 | Node is still v18 after 'nvm use latest'. This project needs Node 20 or newer.
 | The newest version nvm has installed is too old. Install a newer one:
 |     nvm install 24.13.1

The last claim is false, and that is the expensive part. nvm list on this machine reports 26.7.0, 24.13.1 and 18.16.1 — two versions well past the floor. The message sends the reader to install something they already have.

Root cause

nvm use latest does not mean "the newest version I have installed". nvm-windows resolves latest against the remote release list. From its own help:

nvm use [version] [arch] : Switch to use the specified version. Optionally use "latest", "lts", or "newest".
                           "newest" is the latest installed version.

newest is the alias that means the newest installed. latest means the newest that exists. Reproduced directly:

$ nvm use latest
26.8.1
activation error: Version not installed. Run "nvm ls" to see available versions.
$ node --version
v18.16.1

$ nvm use newest
Now using node v26.7.0 (64-bit)

So the switch never happened. NodeVersion.ps1's docstring states the wrong semantics — "nvm use latest picks the highest version nvm has installed locally" — and the code was written against that.

Why the error blamed the wrong thing

Use-Node verifies the switch took, which is the right instinct, but it special-cased the alias:

if ($Version -eq 'latest' -or $raw.TrimStart('v') -eq $Version.TrimStart('v')) {

With $Version of latest that short-circuits to true regardless of what is running. So Use-Node swallowed nvm's activation error: Version not installed — which it had already captured in $output for exactly this purpose — and returned success holding v18.16.1. Use-NodeLatest then checked the major and reported the one explanation that was definitely wrong.

Both halves have to change. Fixing only the alias moves the bug rather than removing it: newest is not latest, so the special case stops matching and Use-Node compares '26.7.0' -eq 'newest', fails twice and throws even though the switch worked. Confirmed by doing exactly that.

Fix

  • Ask for newest, and correct the docstring to record what the two aliases actually mean.
  • Verify an alias switch against nvm's own output rather than assuming it worked, so a genuine failure reports nvm's reason instead of a guess about installed versions.

Verified from a real v18.16.1 baseline: latest (not installed here) now throws and surfaces nvm's reason; Use-NodeLatest switches 18.16.1 → 26.7.0; a concrete uninstalled version still throws correctly. start-local.ps1 then runs the whole way through — database, migrations, backend on 3000.

Affects both entry points

NodeVersion.ps1 is shared by start-local.ps1 and run-tests.ps1 deliberately, so this broke both and one change fixes both.

Open question

There is a hardcoded Use-Node -Version '26.7.0' in the working tree that takes a different approach: it pins the version rather than asking for the newest installed. That works today but it pins a version that has to be maintained by hand and goes stale when 26.7.0 is uninstalled, and Use-NodeLatest is named and documented as "newest installed". Worth settling before this lands.

`.\scripts\start-local.ps1` fails on a machine that has everything it needs: ``` ==> Switching Node to latest (this project needs Node 20 or newer) node v18.16.1 Exception: scripts\NodeVersion.ps1:109 | Node is still v18 after 'nvm use latest'. This project needs Node 20 or newer. | The newest version nvm has installed is too old. Install a newer one: | nvm install 24.13.1 ``` The last claim is false, and that is the expensive part. `nvm list` on this machine reports 26.7.0, 24.13.1 and 18.16.1 — two versions well past the floor. The message sends the reader to install something they already have. ## Root cause `nvm use latest` does not mean "the newest version I have installed". nvm-windows resolves `latest` against the **remote** release list. From its own help: ``` nvm use [version] [arch] : Switch to use the specified version. Optionally use "latest", "lts", or "newest". "newest" is the latest installed version. ``` `newest` is the alias that means the newest installed. `latest` means the newest that exists. Reproduced directly: ``` $ nvm use latest 26.8.1 activation error: Version not installed. Run "nvm ls" to see available versions. $ node --version v18.16.1 $ nvm use newest Now using node v26.7.0 (64-bit) ``` So the switch never happened. `NodeVersion.ps1`'s docstring states the wrong semantics — "`nvm use latest` picks the highest version nvm has installed locally" — and the code was written against that. ## Why the error blamed the wrong thing `Use-Node` verifies the switch took, which is the right instinct, but it special-cased the alias: ```powershell if ($Version -eq 'latest' -or $raw.TrimStart('v') -eq $Version.TrimStart('v')) { ``` With `$Version` of `latest` that short-circuits to true regardless of what is running. So `Use-Node` swallowed nvm's `activation error: Version not installed` — which it had already captured in `$output` for exactly this purpose — and returned success holding v18.16.1. `Use-NodeLatest` then checked the major and reported the one explanation that was definitely wrong. Both halves have to change. Fixing only the alias moves the bug rather than removing it: `newest` is not `latest`, so the special case stops matching and `Use-Node` compares `'26.7.0' -eq 'newest'`, fails twice and throws even though the switch worked. Confirmed by doing exactly that. ## Fix - Ask for `newest`, and correct the docstring to record what the two aliases actually mean. - Verify an alias switch against nvm's own output rather than assuming it worked, so a genuine failure reports nvm's reason instead of a guess about installed versions. Verified from a real v18.16.1 baseline: `latest` (not installed here) now throws and surfaces nvm's reason; `Use-NodeLatest` switches 18.16.1 → 26.7.0; a concrete uninstalled version still throws correctly. `start-local.ps1` then runs the whole way through — database, migrations, backend on 3000. ## Affects both entry points `NodeVersion.ps1` is shared by `start-local.ps1` and `run-tests.ps1` deliberately, so this broke both and one change fixes both. ## Open question There is a hardcoded `Use-Node -Version '26.7.0'` in the working tree that takes a different approach: it pins the version rather than asking for the newest installed. That works today but it pins a version that has to be maintained by hand and goes stale when 26.7.0 is uninstalled, and `Use-NodeLatest` is named and documented as "newest installed". Worth settling before this lands.
bermudalamb added reference feature/198-nvm-node-version 2026-08-27 17:04:57 -05:00
bermudalamb added this to the Code Quality and Hardening 2 project 2026-08-27 17:05:06 -05:00
bermudalamb moved this to Released in Code Quality and Hardening 2 on 2026-08-27 17:05:14 -05:00
bermudalamb self-assigned this 2026-08-27 17:05:21 -05:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bermudalamb/redefined-designs#198