Skip to content

[BUG] npm.ps1 hangs indefinitely when invoked via Node.js spawn({ shell: true }) on Windows #8259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
2 tasks done
viocha opened this issue Apr 26, 2025 · 2 comments
Closed
2 tasks done
Labels
Bug thing that needs fixing

Comments

@viocha
Copy link

viocha commented Apr 26, 2025

Is there an existing issue for this?

  • I have searched the existing issues

This issue exists in the latest npm version

  • I am using the latest npm

Current Behavior

When using Node.js's child_process.spawn function with the shell: true option on Windows to execute npm.ps1 --version, the spawned process prints the correct npm version to stdout but never terminates or triggers the close event on the ChildProcess object. The parent Node.js script hangs indefinitely waiting for the process to close.

In contrast, executing npm.cmd --version using the exact same spawn call works as expected: the version is printed, and the close event is emitted shortly after.

This suggests an issue specifically with how npm.ps1 behaves or interacts with the shell environment (cmd.exe typically, when shell: true is used) when invoked indirectly, potentially related to process lifetime management or stream handling within the PowerShell script or the intermediate powershell.exe process.

Expected Behavior

Both spawn calls should complete successfully. The output should look something like this, and the parent Node.js script should exit shortly after the second close event:

Attempting: spawn("npm.ps1", ["--version"], { shell: true }) - expected to hang
Attempting: spawn("npm.cmd", ["--version"], { shell: true }) - expected to exit
Script setup complete. Waiting for processes...
[npm.cmd] stdout: 10.9.2
[npm.cmd] process closed with code 0
[npm.ps1] stdout: 10.9.2
[npm.ps1] process closed with code 0
// Parent Node.js script exits

Steps To Reproduce

  1. Ensure you are on a Windows machine with Node.js and npm installed.
  2. Save the following code as test-spawn.js:
import { spawn } from "child_process";

console.log('Attempting: spawn("npm.ps1", ["--version"], { shell: true }) - expected to hang');
const spawnResult1 = spawn('npm.ps1', ['--version'], { shell: true });
addListenerToResult(spawnResult1, 'npm.ps1');

console.log('Attempting: spawn("npm.cmd", ["--version"], { shell: true }) - expected to exit');
const spawnResult2 = spawn('npm.cmd', ['--version'], { shell: true });
addListenerToResult(spawnResult2, 'npm.cmd');

function addListenerToResult(result, msg = '') {
    // Log stdout
    result.stdout.on('data', (data) => {
        console.log(`[${msg}] stdout: ${data.toString().trim()}`); // Use toString() and trim
    });

    // Log stderr
    result.stderr.on('data', (data) => {
        console.error(`[${msg}] stderr: ${data.toString().trim()}`); // Use toString() and trim
    });

    // Log errors during spawn itself (e.g., command not found)
    result.on('error', (err) => {
        console.error(`[${msg}] spawn error: ${err}`);
    });

    // Log process exit event
    result.on('close', (code) => {
        console.log(`[${msg}] process closed with code ${code}`);
    });
}

console.log("Script setup complete. Waiting for processes...");
  1. Run the script from your terminal: node test-spawn.js

Observed Behavior:

The script produces output similar to this:

Attempting: spawn("npm.ps1", ["--version"], { shell: true }) - expected to hang
Attempting: spawn("npm.cmd", ["--version"], { shell: true }) - expected to exit
Script setup complete. Waiting for processes...
[npm.cmd] stdout: 10.9.2
[npm.cmd] process closed with code 0
[npm.ps1] stdout: 10.9.2
// Script hangs here indefinitely. The 'close' event for npm.ps1 is never logged.

Environment

  • npm: 10.9.2
  • Node.js: v22.15.0
  • OS Name: Windows11
  • System Model Name: Thinkbook16
  • npm config:
; "builtin" config from D:\Program Files\nodejs\node_modules\npm\npmrc

; prefix = "C:\\Users\\vioch\\AppData\\Roaming\\npm" ; overridden by user

; "user" config from D:\IT\tool\config\npm\.npmrc

loglevel = "http"
prefix = "D:\\ProgramData\\Nodejs\\npm"
registry = "https://registry.npmmirror.com/"
script-shell = "C:\\Program Files\\PowerShell\\7\\pwsh.exe"

; "env" config from environment

userconfig = "D:\\IT\\tool\\config\\npm\\.npmrc"

; node bin location = D:\Program Files\nodejs\node.exe
; node version = v22.15.0
; npm local prefix = C:\Users\vioch\Desktop\desktop-tmp
; npm version = 10.9.2
; cwd = C:\Users\vioch\Desktop\desktop-tmp
; HOME = C:\Users\vioch
; Run `npm config ls -l` to show all defaults.
@viocha viocha added Bug thing that needs fixing Needs Triage needs review for next steps labels Apr 26, 2025
@kristof-low
Copy link

I believe the observed behavior is due to the default configuration for spawn being to pipe the parent's stdio streams to the child process's. The observed hanging is due to the child waiting for the parent to call end() on its stdin stream:

const child = spawn("npm.ps1", ["--version"], {
    shell: true,
});

child.stdin.end();

If you don't want to write anything to the child's stdin, you can use the 'ignore' option for stdio[0] (i.e. stdin):

const child = spawn("npm.ps1", ["--version"], {
    shell: true,
    stdio: ["ignore"], // this is the same as ['ignore', 'pipe', 'pipe'] (I hope)
});

In either case, the observed behavior is as you expect, which is the same as when using npm.cmd.

The reason that npm.cmd doesn't hang is because the batch script doesn't pipe its input to the node process it spawns to execute npm-cli.js:

@REM from npm.cmd

"%NODE_EXE%" "%NPM_CLI_JS%" %*

Which is what npm.ps1 does:

# from npm.ps1

# Support pipeline input
if ($MyInvocation.ExpectingInput) {
  $input | & $NODE_EXE $NPM_CLI_JS $args
}
else {
  & $NODE_EXE $NPM_CLI_JS $args
}

Although, it doesn't seem like npm-cli.js accepts/uses pipeline input in any case?

If this doesn't work, maybe it's to do with your open command string for .ps1 files, which you can find by running the following command in cmd:

ftype Microsoft.PowerShellScript.1

If for some reason your filetype for .ps1 files is not Microsoft.PowerShellScript.1, then find it through

assoc .ps1

@milaninfy
Copy link
Contributor

@viocha ,
Thank you for reporting this issue and providing details about your use case.
The behavior you're experiencing with npm.ps1 is not an issue with the npm CLI itself. The .ps1 script is designed to be used as part of the PowerShell environment and not directly invoked via child_process.spawn. When used as intended, it works as expected.

@milaninfy milaninfy closed this as not planned Won't fix, can't repro, duplicate, stale Apr 29, 2025
@milaninfy milaninfy removed the Needs Triage needs review for next steps label Apr 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug thing that needs fixing
Projects
None yet
Development

No branches or pull requests

3 participants