Version
Current main by source inspection. This appears to affect releases since #55514.
Platform
Microsoft Windows. The behavior is in the _WIN32 path in src/process_wrap.cc.
Subsystem
child_process
What steps will reproduce the bug?
Run this on Windows:
// test.mjsimport{spawn}from'node:child_process';constsubprocess=spawn(process.execPath,['-e','setInterval(() => {}, 1000)']);subprocess.on('error',error=>{console.log('error',error.code);});subprocess.on('exit',(exitCode,signalCode)=>{console.log('exit',{exitCode, signalCode});});console.log('kill returned:',subprocess.kill('SIGWINCH'));setTimeout(()=>{console.log('still running:',!subprocess.killed&&subprocess.exitCode===undefined&&subprocess.signalCode===undefined);subprocess.kill('SIGKILL');},1000);How often does it reproduce? Is there a required condition?
Always on Windows.
What is the expected behavior? Why is that the expected behavior?
SIGWINCH should not forcefully terminate the subprocess on Windows.
Before #55514, unsupported signals such as SIGWINCH reached libuv and returned UV_ENOSYS. In JavaScript, ChildProcess.prototype.kill() handles UV_ENOSYS by throwing an ErrnoException.
That behavior was safer for SIGWINCH: it did not terminate the subprocess.
What do you see instead?
Since #55514, src/process_wrap.cc remaps every Windows signal other than SIGKILL, SIGTERM, SIGINT, SIGQUIT, and 0 to SIGKILL before calling libuv:
#ifdef _WIN32
if (signal != SIGKILL && signal != SIGTERM && signal != SIGINT &&
signal != SIGQUIT && signal != 0) {
signal = SIGKILL;
}
#endifint err = uv_process_kill(&wrap->process_, signal);The current vendored libuv Windows implementation only forcefully terminates for SIGQUIT, SIGTERM, SIGKILL, and SIGINT, treats 0 as a health check, and returns UV_ENOSYS for unsuported signals:
default:
/* Unsupported signal. */returnUV_ENOSYS;
Because Node now remaps SIGWINCH before libuv sees it, subprocess.kill('SIGWINCH') terminates the subprocess as if SIGKILL had been sent.
This is surprising because SIGWINCH is not a termination signal. Users forwarding terminal resize notifications can accidentally kill a child process on Windows.
Additional information
It was already commented here: #42923 (comment) but it was ignored.
Possible fix: exempt SIGWINCH from the Windows remapping in src/process_wrap.cc, so it keeps the previous unsupported-signal behavior instead of becoming SIGKILL.
Version
Current
mainby source inspection. This appears to affect releases since #55514.Platform
Microsoft Windows. The behavior is in the
_WIN32path insrc/process_wrap.cc.Subsystem
child_process
What steps will reproduce the bug?
Run this on Windows:
How often does it reproduce? Is there a required condition?
Always on Windows.
What is the expected behavior? Why is that the expected behavior?
SIGWINCHshould not forcefully terminate the subprocess on Windows.Before #55514, unsupported signals such as
SIGWINCHreached libuv and returnedUV_ENOSYS. In JavaScript,ChildProcess.prototype.kill()handlesUV_ENOSYSby throwing anErrnoException.That behavior was safer for
SIGWINCH: it did not terminate the subprocess.What do you see instead?
Since #55514,
src/process_wrap.ccremaps every Windows signal other thanSIGKILL,SIGTERM,SIGINT,SIGQUIT, and0toSIGKILLbefore calling libuv:The current vendored libuv Windows implementation only forcefully terminates for
SIGQUIT,SIGTERM,SIGKILL, andSIGINT, treats0as a health check, and returnsUV_ENOSYSfor unsuported signals:Because Node now remaps
SIGWINCHbefore libuv sees it,subprocess.kill('SIGWINCH')terminates the subprocess as ifSIGKILLhad been sent.This is surprising because
SIGWINCHis not a termination signal. Users forwarding terminal resize notifications can accidentally kill a child process on Windows.Additional information
It was already commented here: #42923 (comment) but it was ignored.
Possible fix: exempt
SIGWINCHfrom the Windows remapping insrc/process_wrap.cc, so it keeps the previous unsupported-signal behavior instead of becomingSIGKILL.