Repository navigation
subprocess.kill('SIGWINCH') forcefully terminates subprocesses on Windows #64324
Description
Activity
@sindresorhus Just to confirm the behavior you expect, consider the following snippet, which is similar to yours:
import {spawn} from 'node:child_process'; const subprocess = 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}); }); try { console.log('kill returned:', subprocess.kill('SIGWINCH')); } catch (err) { console.log('kill failed:', err.code); // ENOSYS on Windows } setTimeout(() => { const stillRunning = !subprocess.killed && subprocess.exitCode === null && subprocess.signalCode === null; console.log('still running:', stillRunning); subprocess.kill('SIGKILL'); }, 1000);
After running it with the official node binary, the result is the following:
PS C:\64324> node .\index.mjs kill returned: true exit { exitCode: null, signalCode: 'SIGKILL' } still running: false
After running it with the changes you proposed:
PS C:\64324> ..\node\Release\node.exe .\index.mjs kill failed: ENOSYS still running: true exit { exitCode: null, signalCode: 'SIGKILL' }
As you can see, in the second run,
subprocess.kill('SIGWINCH')throws an exception. Is that the behavior you expect? It seems to contradict the requirements in the issue you referenced (#42923).Yes,
ENOSYSis the behavor I expect, and I don't think it contradicts #42923.That issue was about termination signals like
SIGHUP, where "ignore the signal and kill likeSIGKILL" is a reasonable Windows fallback.SIGWINCHis categorically different: it's a non-fatal notification (terminal resize) whose default action on POSIX is to be ignored. It should never terminate a process.So the two positions are compatible:
- Termination signals -> behave like
SIGKILL(persubprocess.killshould ignoresignalargument on Windows #42923). - Non-terminating signals like
SIGWINCH-> don't kill.
Throwing
ENOSYS(the pre-#55514 behavior) is fine and the simplest fix. A silent no-op would be even nicer, but the important thing is just that it doesn't silentlySIGKILLthe child.- Termination signals -> behave like
@sindresorhus There was already documentation before the issue you're referencing.
It explicitly states that, on Windows, the
signalargument is ignored and the process is terminated forcefully and abruptly. So the expectations described in this issue are inconsistent with both the documentation and the current behavior, meaning the current behavior is expected.That makes this issue a feature request: start throwing exceptions for unsupported signals on Windows instead of treating them as
SIGKILL. It also wouldn't make sense to special-case onlySIGWINCH. If the behavior is changed, it should apply consistently to all unsupported non-terminating signals, not justSIGWINCH, and the documentation should be updated accordingly to reflect that behavior.That doc line is aspirational, not descriptive. It was added in #34867 back in 2020, but the runtime never actually behaved that way for non-terminating signals. For over a decade
kill('SIGWINCH')andkill('SIGHUP')returnedUV_ENOSYSand threw. The author of #55514 even says so ("it throws an exception with the codeENOSYS. This is not consistent with the documentation"), and #42923 flagged the ambiguity directly. So #55514 changed the code to match the doc, it didn't document what the code already did, and it did that by making it worse:kill('SIGWINCH')went from throwing (harmless) to silently force-killing the child, with no notable-change flag.The doc wording is also contradictory. "Ignored ... similar to
SIGKILL" are opposites, and on POSIX the default disposition ofSIGWINCHis actually Ignore (same forSIGCHLDandSIGURG). Mapping it toSIGKILLinverts the signal, it doesn't ignore it.I agree it shouldn't special-case
SIGWINCH. The consistent fix is just to revert what #55514 did and let unsupported signals throwUV_ENOSYSagain. One line, backward compatible, non-destructive, same for every signal, and anyone who wants a hard kill still hasSIGKILLandSIGTERM. Then update the docs to say unsupported signals throw instead of claiming they're ignored.- added a commit that references this issue
on Aug 21, 2026 - added 2 commits that reference this issue
on Sep 7, 2026
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.