Skip to content

fix: use npm co-located with the action node binary#239

Merged
zkochan merged 3 commits intopnpm:masterfrom
benquarmby:fix/npm-path-ghe-runners
Apr 30, 2026
Merged

fix: use npm co-located with the action node binary#239
zkochan merged 3 commits intopnpm:masterfrom
benquarmby:fix/npm-path-ghe-runners

Conversation

@benquarmby
Copy link
Copy Markdown
Contributor

@benquarmby benquarmby commented Apr 27, 2026

Fixes #234spawn npm ENOENT on GHE self-hosted runners where npm is not on PATH.

Root cause

#212 introduced npm ci as the bootstrap mechanism. On standard GitHub-hosted runners npm is on PATH; on GHE self-hosted runners it is not guaranteed to be present.

Fix

  • Resolve npm via join(dirname(process.execPath), 'npm'[.cmd]) - npm is always co-located with the node binary that runs the action CI disproved this statement. An alternative approach was required. See follow up commits for correction.
  • Prepend dirname(process.execPath) to the child PATH so npm's #!/usr/bin/env node shebang can resolve node

Test plan

  • GHE self-hosted runner without npm on PATH
  • Standard GitHub-hosted Linux, macOS, and Windows runners

Comment thread src/install-pnpm/run.ts Outdated
Follow-up to 5a9e198. Two refinements to the GHE self-hosted runner fix:

- Spawn npm via `path.join(dirname(process.execPath), 'npm[.cmd]')`
  instead of relying on PATH lookup. This matches the original PR
  description and is robust against PATH-shadowed npm installations.
- Avoid `"<dir>:undefined"` leaking into PATH when `process.env.PATH`
  is unset (rare, but possible in stripped environments).

PATH still has the node directory prepended so npm's
`#!/usr/bin/env node` shebang can resolve node on Linux/macOS.
Revert 42e75a1's switch to absolute-path npm resolution. The premise
that npm is co-located with the action's node binary is false on
GitHub-hosted runners: `process.execPath` points into
`runner/externals/node24/bin/`, which contains node only — not npm.
The absolute-path spawn produced ENOENT on Linux/macOS and
"not recognized" on Windows.

Go back to spawning `'npm'` and relying on PATH lookup, which works
on standard runners (npm is on PATH from the runner image) and on
the GHE self-hosted setup that motivated the original fix. Keep the
node-directory prepend so npm's `#!/usr/bin/env node` shebang
resolves, and keep the unset-PATH guard.
@zkochan
Copy link
Copy Markdown
Member

zkochan commented Apr 30, 2026

Heads up — I pushed two follow-up commits and want to flag a finding from the CI run.

42e75a1 (broken, reverted by f9d6871) attempted to resolve npm via an absolute path: path.join(dirname(process.execPath), 'npm[.cmd]'). This is what the original PR description claimed, but it turns out to be incorrect for GitHub-hosted runners.

On the runner, process.execPath is the runner-bundled node, e.g.:

  • Linux: /home/runner/actions-runner/extracted/externals/node24/bin/node
  • Windows: C:\actions-runner\cached\2.334.0\externals\node24\bin\node.exe

That externals/node24/bin/ directory contains node only — npm is not there. The runner ships only the node binary it needs to execute JS actions; npm comes from the runner image's pre-installed Node toolchain and is reachable via PATH.

Result of the bad commit:

  • Linux/macOS: Error: spawn /home/runner/.../externals/node24/bin/npm ENOENT
  • Windows: 'C:\actions-runner\...\externals\node24\bin\npm.cmd' is not recognized as an internal or external command

f9d6871 reverts to spawning 'npm' and relying on PATH lookup (the actual behavior of 5a9e198, despite its description), while keeping two refinements that are still worthwhile:

  1. Prepend dirname(process.execPath) to PATH — needed so npm's #!/usr/bin/env node shebang resolves on Linux/macOS in stripped environments where node isn't already on PATH. This is the real GHE self-hosted fix.
  2. Guard against unset PATHcurrentPath ? nodeDir + delimiter + currentPath : nodeDir avoids "<dir>:undefined" leaking in.

If you want, the PR description should probably be updated to drop the "npm is always co-located with the node binary" claim, since CI just disproved it.

@zkochan zkochan merged commit 26f6d4f into pnpm:master Apr 30, 2026
32 of 33 checks passed
@silas
Copy link
Copy Markdown

silas commented Apr 30, 2026

fyi: after this was published, I started getting the following error:

Unable to resolve action pnpm/action-setup@v6, unable to find version v6

Updating my workflows to use the exact tag fixed the issue:

uses: pnpm/action-setup@v6.0.4

Anyway, this is probably how you're suppose to reference releases in the action marketplace and is a good change for me anyway, but just a heads up.

@zkochan
Copy link
Copy Markdown
Member

zkochan commented Apr 30, 2026

The v6 tag was removed just for a moment before I pointed it to a new commit. It should work. However, the recommended way is to use the commit hash. It is the secure way.

@benquarmby
Copy link
Copy Markdown
Contributor Author

Updated the PR description, and thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v6 fails if npm does not exist on the runner (GHE)

3 participants