Skip to content

Commit d322973

Browse files
mtorpclaude
andauthored
fix: prefer system npm/npx over project-local versions from node_modules (#1172)
When the CLI runs inside an npm script (or any context where node_modules/.bin is on PATH), getNpxBinPath() could pick up a project-local npx instead of the system one. The standalone npx package (npx@10.2.2) bundles npm@5.1.0 which is incompatible with Node 22+, causing "cb.apply is not a function" errors during Coana reachability analysis. Fix: check for npm/npx next to process.execPath (the running node binary) before falling back to PATH-based lookup. This follows the same pattern already used by findRealNpm() in @socketsecurity/registry and getAgentExecPath() in package-environment.mts. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent db0261b commit d322973

File tree

1 file changed

+36
-2
lines changed

1 file changed

+36
-2
lines changed

src/utils/npm-paths.mts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs'
22
import Module from 'node:module'
33
import path from 'node:path'
44

5+
import { resolveBinPathSync } from '@socketsecurity/registry/lib/bin'
56
import { logger } from '@socketsecurity/registry/lib/logger'
67

78
import constants, { NODE_MODULES, NPM } from '../constants.mts'
@@ -19,6 +20,23 @@ function exitWithBinPathError(binName: string): never {
1920
throw new Error('process.exit called')
2021
}
2122

23+
// Find a binary next to the running node binary (process.execPath).
24+
// This avoids picking up a project-local binary from node_modules/.bin
25+
// on PATH, e.g. the standalone "npx" package which bundles npm@5.1.0
26+
// that is incompatible with Node 22+.
27+
function findBinNextToNode(binName: string): string | undefined {
28+
const nodeDir = path.dirname(process.execPath)
29+
const binPath = path.join(nodeDir, binName)
30+
if (existsSync(binPath)) {
31+
try {
32+
return resolveBinPathSync(binPath)
33+
} catch {
34+
return undefined
35+
}
36+
}
37+
return undefined
38+
}
39+
2240
let _npmBinPath: string | undefined
2341
export function getNpmBinPath(): string {
2442
if (_npmBinPath === undefined) {
@@ -33,7 +51,14 @@ export function getNpmBinPath(): string {
3351
let _npmBinPathDetails: ReturnType<typeof findBinPathDetailsSync> | undefined
3452
function getNpmBinPathDetails(): ReturnType<typeof findBinPathDetailsSync> {
3553
if (_npmBinPathDetails === undefined) {
36-
_npmBinPathDetails = findBinPathDetailsSync(NPM)
54+
// First try to find npm next to the node binary to avoid picking up
55+
// a project-local npm from node_modules/.bin on PATH.
56+
const npmNextToNode = findBinNextToNode(NPM)
57+
if (npmNextToNode) {
58+
_npmBinPathDetails = { name: NPM, path: npmNextToNode, shadowed: false }
59+
} else {
60+
_npmBinPathDetails = findBinPathDetailsSync(NPM)
61+
}
3762
}
3863
return _npmBinPathDetails
3964
}
@@ -95,7 +120,16 @@ export function getNpxBinPath(): string {
95120
let _npxBinPathDetails: ReturnType<typeof findBinPathDetailsSync> | undefined
96121
function getNpxBinPathDetails(): ReturnType<typeof findBinPathDetailsSync> {
97122
if (_npxBinPathDetails === undefined) {
98-
_npxBinPathDetails = findBinPathDetailsSync('npx')
123+
// First try to find npx next to the node binary to avoid picking up
124+
// a project-local npx from node_modules/.bin on PATH (e.g., the
125+
// standalone npx package which bundles npm@5.1.0, incompatible
126+
// with Node 22+).
127+
const npxNextToNode = findBinNextToNode('npx')
128+
if (npxNextToNode) {
129+
_npxBinPathDetails = { name: 'npx', path: npxNextToNode, shadowed: false }
130+
} else {
131+
_npxBinPathDetails = findBinPathDetailsSync('npx')
132+
}
99133
}
100134
return _npxBinPathDetails
101135
}

0 commit comments

Comments
 (0)