fix: Fixed an issue with files named node.js on Windows - #64
Merged
Merged
Conversation
5 tasks
kevinoid
added a commit
to kevinoid/eslint-config-kevinoid
that referenced
this pull request
May 7, 2023
To avoid inadvertently running node.js (using Windows Script Host) instead of node.exe when .js is present in %PATHEXT% (as it is by default). The problem is exacerbated by a regression in .cmd shims generated by npm (npm/cmd-shim#64 npm/cmd-shim#71) and has already caused problems in CI (https://github.com/actions/setup-node/issues/720). Continue to export node and node.js from the package for backward compatibility. These may be removed in a future version. Signed-off-by: Kevin Locke <kevin@kevinlocke.name>
|
Sorry this got stale. Reopen a pr if it is still good/relevant |
kevinoid
added a commit
to kevinoid/setup-node-issue-720
that referenced
this pull request
Jun 10, 2026
See: npm/cmd-shim#71 See: npm/cmd-shim#64 Signed-off-by: Kevin Locke <kevin@kevinlocke.name>
|
@owlstronaut I can still reproduce the issue in echo #!/usr/bin/env node >mycommand.js
echo console.log('Success!') >>mycommand.js
md bin
node -e "require('cmd-shim')('mycommand.js', 'bin/mycommand')"
type nul >bin/node.js
cd bin
mycommandI can also confirm that this PR still applies cleanly and fixes the issue, as demonstrated by a Github Actions Workflow. I urge you to consider reopening and merging this PR. |
|
@kevinoid great, I've reopened it. Happy to take a look when CI is happy :) |
Contributor
Author
|
It seems in the last 4 years a test has been added which my original commit didn't cover. I've updated the snapshot. It should pass now. |
owlstronaut
approved these changes
Jun 19, 2026
Merged
owlstronaut
pushed a commit
that referenced
this pull request
Jun 19, 2026
🤖 I have created a release *beep* *boop* --- ## [9.0.2](v9.0.1...v9.0.2) (2026-06-19) ### Bug Fixes * [`1c2f94c`](1c2f94c) [#64](#64) Fixed an issue with files named node.js on Windows (#64) (@giovannicalo) ### Chores * [`4f2209b`](4f2209b) [#185](#185) bump @npmcli/template-oss from 5.1.0 to 5.1.1 (#185) (@dependabot[bot], @npm-cli-bot) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
liustack
added a commit
to liustack/modlens
that referenced
this pull request
Aug 16, 2026
…atch (#43) npm installs a compiled CLI as a .cmd that runs the .exe beside it, and handing that .cmd to spawn is the EINVAL that broke the claude-cli fallback and the opencode reuse probe. The first attempt at this classified each line against a whitelist; eight review rounds found eight defects in it, every one the same shape: a line cannot be judged on its own, because whether it runs at all is a property of the file. The last was an IF EXIST whose condition is false, where cmd runs nothing and the reader still produced a plan that spawned the provider. That trades a loud failure for a silent wrong action, so it was withdrawn from 3.17.1 rather than shipped. This does not parse batch. It recognises the shapes the two real generators emit, each carrying its own closed recipe. No block tree, no free composition, which is where every defect lived. Anything else is declined and the caller's own spawn error stands. npm's Node shim is two templates, because the PATHEXT edit changes meaning between them. Through 9.0.1 it sits inside SETLOCAL while the execution line begins with endLocal, so it is undone before the lookup and before the child starts; honouring it would reproduce a bug npm fixed in 9.0.2 (npm/cmd-shim#64). From 9.0.2 it rides the execution line after endLocal, where it applies, on either arm. pnpm writes it inside the ELSE block and never calls endlocal, so it applies to that arm alone. npm's native template calls no ENDLOCAL at all, so its child inherits the dp0 the prologue set, and the plan carries it. Bare `node` is resolved the way cmd resolves it: the exact name before any PATHEXT candidate, the working directory before PATH unless NoDefaultCurrentDirectoryInExePath says otherwise, relative PATH entries anchored to that directory, and a hit that is itself a batch file declined. There is no process.execPath fallback, which would run a different Node than the shell whenever a portable install or an overridden PATH is involved. The existence test is the sharp edge. Its operand is never taken from the file: each recipe tests the one candidate it is about to run, built from the shim's own directory, and the file has to name exactly that. A path captured out of an untrusted file and handed to the filesystem is a file-existence oracle, and a UNC or device path turns a check into network I/O and authentication that reading a local shim never authorised. Relative, drive-relative, root-relative, UNC and device paths are refused before the file is opened, not after. Existence is tri-state, because existsSync folds a permission error into "absent" and absent decides which program runs. Environment handling mirrors what Node actually passes on Windows: keys folded case-insensitively, PATHEXT substitution case-insensitive the way cmd does it. analyzer built the child environment after planning, so the planner read one environment while the child got another; it is built first now, and the spawn cwd reaches the planner because cmd's lookup depends on it. The reuse probe and the Pi key fetch pass the plan's environment through for the same reason. Fixtures are verbatim generator output with each version installed in its own directory, after an earlier attempt asked npm for two versions of one package in a single install and silently got the older.
sobermh
pushed a commit
to TokensAPI/tokens_DshModelManager_code
that referenced
this pull request
Aug 19, 2026
…atch (#43) npm installs a compiled CLI as a .cmd that runs the .exe beside it, and handing that .cmd to spawn is the EINVAL that broke the claude-cli fallback and the opencode reuse probe. The first attempt at this classified each line against a whitelist; eight review rounds found eight defects in it, every one the same shape: a line cannot be judged on its own, because whether it runs at all is a property of the file. The last was an IF EXIST whose condition is false, where cmd runs nothing and the reader still produced a plan that spawned the provider. That trades a loud failure for a silent wrong action, so it was withdrawn from 3.17.1 rather than shipped. This does not parse batch. It recognises the shapes the two real generators emit, each carrying its own closed recipe. No block tree, no free composition, which is where every defect lived. Anything else is declined and the caller's own spawn error stands. npm's Node shim is two templates, because the PATHEXT edit changes meaning between them. Through 9.0.1 it sits inside SETLOCAL while the execution line begins with endLocal, so it is undone before the lookup and before the child starts; honouring it would reproduce a bug npm fixed in 9.0.2 (npm/cmd-shim#64). From 9.0.2 it rides the execution line after endLocal, where it applies, on either arm. pnpm writes it inside the ELSE block and never calls endlocal, so it applies to that arm alone. npm's native template calls no ENDLOCAL at all, so its child inherits the dp0 the prologue set, and the plan carries it. Bare `node` is resolved the way cmd resolves it: the exact name before any PATHEXT candidate, the working directory before PATH unless NoDefaultCurrentDirectoryInExePath says otherwise, relative PATH entries anchored to that directory, and a hit that is itself a batch file declined. There is no process.execPath fallback, which would run a different Node than the shell whenever a portable install or an overridden PATH is involved. The existence test is the sharp edge. Its operand is never taken from the file: each recipe tests the one candidate it is about to run, built from the shim's own directory, and the file has to name exactly that. A path captured out of an untrusted file and handed to the filesystem is a file-existence oracle, and a UNC or device path turns a check into network I/O and authentication that reading a local shim never authorised. Relative, drive-relative, root-relative, UNC and device paths are refused before the file is opened, not after. Existence is tri-state, because existsSync folds a permission error into "absent" and absent decides which program runs. Environment handling mirrors what Node actually passes on Windows: keys folded case-insensitively, PATHEXT substitution case-insensitive the way cmd does it. analyzer built the child environment after planning, so the planner read one environment while the child got another; it is built first now, and the spawn cwd reaches the planner because cmd's lookup depends on it. The reuse probe and the Pi key fetch pass the plan's environment through for the same reason. Fixtures are verbatim generator output with each version installed in its own directory, after an earlier attempt asked npm for two versions of one package in a single install and silently got the older.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When a Node module binary is run in a folder containing a file called
node.js, on Windows, that file will be opened instead of the binary.This happens due to Windows'
PATHEXTenvironment variable containing.JS.The issue was originally fixed in 8b1433c, but it has reappeared in c039646.
Currently,
endlocalis called after editingPATHEXTbut before invoking Node, so the latter is not affected by it.My solution edits
PATHEXTafter callingendlocal, thus affecting the Node invocation and fixing the issue.The only downside is the Node process will receive the edited
PATHEXT, which however must've been the original behaviour anyway.