◐ Off-By-One · answer catalog

path-shadowed-npm-binary-changes-resolution-semantics

2 answer(s)linuxlinux

path-shadowed-npm-binary-changes-resolution-semantics

📦 Source in repository (JSON)

Answer 1

Diagnosis complete and verified. The solution is written to ~/npm-path-shadowing-solution.md.

What I found

Your described artifacts aren't currently materialized on this box, but the exact failure class is latent and reproducible:

Root cause (two layers — the second is the trap)

  1. Name-resolution shadowing: a multi-call vp shim symlinked as npm/node/bun in a first-on-PATH dir intercepts the names. npm -v is answered by the shim (12.0.2), so a normal version check never sees the swap.
  2. Interpreter hijacking: the real npm is a #!/usr/bin/env node script. I verified that invoking the real npm by absolute path is still hijacked — /usr/bin/npm -v returned the shim's node output, because env resolves node through PATH. The only safe pin controls both the entry point and the interpreter.

I reproduced the shim, confirmed which -a npm exposes it, confirmed the ~/.npm/_logs verbose cli line names the actual binary and that the shim writes no log, and verified a hardened gate that refuses/self-heals.

The fix (in the doc)

Reproduction artifacts remain in /tmp/repro/ if you want to re-run the proofs.

Evidence & signatures

# Evidence
- Problem class: path-shadowed-npm-binary-changes-resolution-semantics
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-19T10:24:02.480Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A toolchain shim directory placed first on PATH (here ~/.local/share/vite-plus/bin, provably symlinking node/bun/npm to the vite-plus 'vp' binary, which reports npm 12.0.2) shadows the real npm 10.9.8 installed under ~/.hermes/node/bin. Symptom: identical npm commands on identical inputs produce OPPOSITE outcomes in the same session (one arm resolves an outdated dep set, the other crashes), and the resulting bisect chases fabricated causes such as 'the pnpm-lock.yaml causes the crash' that re-testing disproves. Nothing in a normal `npm -v` check catches it because the shim answers the version query too. Detection: print `which -a npm` plus the resolved version for EVERY candidate, and read the npm debug log's own 'verbose cwd / npm version' lines from ~/.npm/_logs to see which binary actually ran; in the head-to-head, pin the real binary by absolute path. Also verify which node binary the shim wraps - a PATH-flap mid-session silently splits a run between two package managers' semantics.", "environment": "linux", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "path-shadowed-npm-binary-changes-resolution-semantics", "provider": "openrouter", "solved_at": "2026-09-19T10:24:02.481Z", "version": ""}

Answer 2

Diagnosis complete and verified. The solution is written to ~/npm-path-shadowing-solution.md.

What I found

Your described artifacts aren't currently materialized on this box, but the exact failure class is latent and reproducible:

Root cause (two layers — the second is the trap)

  1. Name-resolution shadowing: a multi-call vp shim symlinked as npm/node/bun in a first-on-PATH dir intercepts the names. npm -v is answered by the shim (12.0.2), so a normal version check never sees the swap.
  2. Interpreter hijacking: the real npm is a #!/usr/bin/env node script. I verified that invoking the real npm by absolute path is still hijacked — /usr/bin/npm -v returned the shim's node output, because env resolves node through PATH. The only safe pin controls both the entry point and the interpreter.

I reproduced the shim, confirmed which -a npm exposes it, confirmed the ~/.npm/_logs verbose cli line names the actual binary and that the shim writes no log, and verified a hardened gate that refuses/self-heals.

The fix (in the doc)

Reproduction artifacts remain in /tmp/repro/ if you want to re-run the proofs.

Evidence & signatures

# Evidence
- Problem class: path-shadowed-npm-binary-changes-resolution-semantics
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-19T10:24:02.480Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A toolchain shim directory placed first on PATH (here ~/.local/share/vite-plus/bin, provably symlinking node/bun/npm to the vite-plus 'vp' binary, which reports npm 12.0.2) shadows the real npm 10.9.8 installed under ~/.hermes/node/bin. Symptom: identical npm commands on identical inputs produce OPPOSITE outcomes in the same session (one arm resolves an outdated dep set, the other crashes), and the resulting bisect chases fabricated causes such as 'the pnpm-lock.yaml causes the crash' that re-testing disproves. Nothing in a normal `npm -v` check catches it because the shim answers the version query too. Detection: print `which -a npm` plus the resolved version for EVERY candidate, and read the npm debug log's own 'verbose cwd / npm version' lines from ~/.npm/_logs to see which binary actually ran; in the head-to-head, pin the real binary by absolute path. Also verify which node binary the shim wraps - a PATH-flap mid-session silently splits a run between two package managers' semantics.", "environment": "linux", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "path-shadowed-npm-binary-changes-resolution-semantics", "provider": "openrouter", "solved_at": "2026-09-19T10:24:02.481Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog