[BUG] Claude Desktop NodeJS Env + NVM Path Sorting

Open 💬 3 comments Opened Apr 28, 2026 by PseudoSky

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Summary

Claude Desktop's PATH builder enumerates ~/.nvm/versions/node/*/bin via glob and prepends the results to the spawned shell's PATH. A recent version (1.4758.0, observed via the bundled app.asar) added a .reverse() on the glob results so the lexicographically-largest directory name comes first, which usually means the newest Node wins. That partially fixes the original symptom, but the underlying mechanism is lexicographic ordering of directory names, not semantic version resolution — so it can still pick the wrong version for ordinary nvm layouts.

Related (same Desktop-app PATH-construction code path, different symptoms): [#42248](https://github.com/anthropics/claude-code/issues/42248), [#46954](https://github.com/anthropics/claude-code/issues/46954). Likely-misdiagnosed prior report of the same symptom: [#34392](https://github.com/anthropics/claude-code/issues/34392) (closed without identifying the glob enumeration).

Evidence and version comparison

Caveat on the "before/after" framing: I observed the runtime behavior of an earlier Claude Desktop build (no version captured at the time, sometime before 2026-04-27), and I extracted the source of the current build (1.4758.0). I do not have the earlier build's app.asar to diff against. The comparison below is observed-symptom-then vs. read-source-now, not a source-level diff.

Earlier build — observed runtime symptom only. Inside a Claude Code Bash with ~/.nvm/versions/node/{v16.14.2,v17.8.0,v20.11.0,v24.11.1} installed and nvm alias default v24.11.1:

$ echo "$PATH" | tr ':' '\n' | grep nvm
/Users/me/.nvm/versions/node/v16.14.2/bin
/Users/me/.nvm/versions/node/v17.8.0/bin
/Users/me/.nvm/versions/node/v20.11.0/bin
/Users/me/.nvm/versions/node/v24.11.1/bin
$ node --version
v16.14.2

Glob results were prepended in forward lexicographic order, so the lexicographically-smallest directory (v16.14.2) won.

Current build (1.4758.0) — extracted source. From /Applications/Claude.app/Contents/Resources/app.asar via grep -aoE:

function pgr() {
  const e = yi.homedir();
  return zr ? [
    `${e}/.nvm/versions/node/*/bin`,
    "/opt/homebrew/Caskroom/miniforge/base/envs/py*/bin",
    "/usr/local/bin",
    "/opt/homebrew/bin",
    …
  ] : …
}

async function mgr() {
  let e = [];
  for (const i of pgr()) {
    const r = [];
    for await (const n of te.glob(i)) r.push(n);
    r.reverse();                                  // ← changed
    e = [...e, ...r];
  }
  const A = process.env.PATH?.split(rA.delimiter) ?? [];
  return Array.from(new Set([...e, ...A]));
}

r.reverse() is the only relevant change inferable from the symptom shift. After reversing, v24.11.1 precedes v16.14.2, which is what fixed the case I originally hit. mgr() produces the final export PATH=... injected at the end of every shell snapshot under ~/.claude/shell-snapshots/snapshot-zsh-*.sh.

If anyone reading this issue has an earlier app.asar archived, please attach the corresponding mgr()/pgr() definitions so the diff can be made source-to-source rather than inferred.

What the .reverse() does and does not fix

It fixes the original alphabetical-first symptom for the common nvm layout where every directory has the same number of digits in the major component:

v16.14.2  v17.8.0  v20.11.0  v24.11.1

glob() returns these in lexicographic order; reversing puts v24.11.1 first.

It does not fix the underlying mechanism — it relies on lexicographic order matching semver order, which is only true accidentally. Real cases where it still picks wrong:

  1. Major-version digit count differs. Lexicographic compare is character-by-character. v9.0.0 > v10.0.0 and v9.0.0 > v24.0.0 because 9 > 1 and 9 > 2 as characters. After .reverse(), v9.0.0 wins over v24.0.0. Anyone who ever installed Node 8 or 9 alongside a current major hits this.
  1. Minor/patch digit counts differ within the same major. v20.10.0 vs. v20.9.0 — lexicographically v20.9.0 > v20.10.0 because 9 > 1 at the first differing character. After .reverse(), v20.9.0 wins.
  1. Pre-release / candidate suffixes. v20.0.0-rc.1 and v20.0.0 sort by string compare, which has no relationship to semver precedence.
  1. Non-nvm directory names. iojs-v3.x or other historical entries that nvm can leave behind sort however their leading character lands.
  1. Project intent ignored. Even when the glob happens to land on a version that matches the user's nvm default, that's coincidence. A project's .nvmrc is never consulted.

Workaround

Until the resolver is fixed, uninstall Node versions you don't need so the glob can only find one:

nvm reinstall-packages <old>   # if you need globals from an older version
nvm uninstall v9.x.x v16.x.x …

After restarting Claude Code the snapshot regenerates with only the remaining version on PATH.

What Should Happen?

Stop relying on directory-name order. Resolve a single Node version explicitly, then prepend only that one bin:

  1. Project-local .nvmrc in the working directory (or any ancestor).
  2. ~/.nvm/alias/default (the user's nvm default).
  3. As a last resort, parse the directory names as semver (semver.rsort) — not lexicographic order — and pick the highest.

If preserving every installed version on PATH is intentional (so nvm use can switch within a session), at minimum sort the glob results with a semver comparator, not lexicographically, and put the resolved active version first.

Error Messages/Logs

Two concrete error blocks I captured live in your session — both directly caused by Claude Desktop selecting `v16.14.2` instead of your nvm default `v24.11.1`:

---

**1. `markdownlint-cli2` aborting in a `PostToolUse` lint hook**

This is the original symptom that started the investigation. Captured by running the hook against `README.md` while Claude Desktop's snapshot pinned PATH to v16:


markdownlint (auto-fix ran) — unfixable problems in /Users/me/dev/repo/README.md:

file:///Users/me/dev/repo/node_modules/.pnpm/string-width@8.1.0/node_modules/string-width/index.js:16
const zeroWidthClusterRegex = /^(?:\p{Default_Ignorable_Code_Point}|\p{Control}|\p{Mark}|\p{Surrogate})+$/v;
                              ^

SyntaxError: Invalid regular expression flags
    at ESMLoader.moduleStrategy (node:internal/modules/esm/translators:115:18)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:289:14)


The regex `v` flag was added in Node 20. Under Node 16 every invocation of `markdownlint-cli2@0.22.1` crashes at module load. Because the hook returned non-zero, the agent's `Write` was hard-blocked.

---

**2. `npm install` ETARGET in a `SessionStart` hook**

Captured at session boot (the very first system reminder of the session). The session's npm was the v16.14.2-bundled npm 8.x, which failed to resolve `chokidar@^5.0.0`:


SessionStart:startup hook success: npm ERR! code ETARGET
npm ERR! notarget No matching version found for chokidar@^5.0.0.
npm ERR! notarget In most cases you or one of your dependencies are requesting
npm ERR! notarget a package version that doesn't exist.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/me/.npm/_logs/2026-04-28T00_09_32_788Z-debug-0.log


The `chokidar@5.x` line *does* exist on the public npm registry; the error was npm 8.x failing to resolve a manifest it didn't understand. After uninstalling Node 16 and switching to v24's npm 11, the same `pnpm install` succeeded without changes to `package.json`.

Steps to Reproduce

Reproduction (still demonstrable on 1.4758.0)

Install Node majors that span a digit-width boundary:

nvm install 9
nvm install 24
nvm alias default 24

In a fresh terminal: node --versionv24.x.x.

Open a new Claude Code session and run node --version in Bash → v9.x.x, because v9... > v24... lexicographically and .reverse() puts the lexicographic-greatest first.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.121

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

_No response_

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗