claude-code - 💡(How to fix) Fix [BUG] Claude Desktop NodeJS Env + NVM Path Sorting [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#54135Fetched 2026-04-28 06:38:19
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
0
Author
Participants
Timeline (top)
labeled ×4

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).

Error Message

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/[email protected]/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 [email protected] 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 [email protected] 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.

Root Cause

  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.

Fix Action

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.

Code Example

$ 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

---

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]));
}

---

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

---

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

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/[email protected]/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 `[email protected]` 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 `[email protected]` 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`.

---

nvm install 9
nvm install 24
nvm alias default 24
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • 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.

  2. 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.

  3. Pre-release / candidate suffixes. v20.0.0-rc.1 and v20.0.0 sort by string compare, which has no relationship to semver precedence.

  4. Non-nvm directory names. iojs-v3.x or other historical entries that nvm can leave behind sort however their leading character lands.

  5. 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/[email protected]/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 `[email protected]` 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 `[email protected]` 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

extent analysis

TL;DR

The issue can be temporarily worked around by uninstalling unnecessary Node versions so that the glob can only find one version.

Guidance

  • The problem arises from Claude Desktop's PATH builder using lexicographic ordering of directory names instead of semantic version resolution, which can lead to the wrong Node version being selected.
  • To verify the issue, install Node majors that span a digit-width boundary (e.g., nvm install 9 and nvm install 24) and check the Node version in a new Claude Code session.
  • A temporary workaround is to uninstall Node versions that are not needed, so the glob can only find one version.
  • The long-term solution should involve resolving a single Node version explicitly, using methods such as checking the project-local .nvmrc file, the user's nvm default, or parsing directory names as semver.

Example

To uninstall unnecessary Node versions, use the command nvm uninstall <version> (e.g., nvm uninstall v9.x.x).

Notes

The provided workaround may not be suitable for all users, especially those who need multiple Node versions installed. A more robust solution would require changes to Claude Desktop's PATH builder to use semantic version resolution.

Recommendation

Apply the workaround by uninstalling unnecessary Node versions until a proper fix is implemented that resolves the Node version based on semantic versioning rules.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] Claude Desktop NodeJS Env + NVM Path Sorting [1 participants]