Repository navigation
spawn cleanup for a process with a SIGINT handler can leave stdin in echo mode #51238
Description
Version
v21.5.0
Platform
Linux hostname 6.5.0-14-generic #14-Ubuntu SMP PREEMPT_DYNAMIC Tue Nov 14 14:59:49 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
Subsystem
child_process
What steps will reproduce the bug?
- Place the following in a file called
gnarly.js
#!/usr/bin/env node
const cp = require("child_process");
if (process.env.CHILD) {
process.once("SIGINT", () => {});
process.stdin.resume();
} else {
cp.spawn("./gnarly.js", {
stdio: [process.stdin, "pipe", process.stderr],
env: { CHILD: 1, ...process.env },
});
}chmod +x gnarly.js- Run
./gnarly.js - interrupt it with ctrl-c
- Try to use the up or down arrow keys in your shell after a second or so, and realize your shell is stuck in echo mode
How often does it reproduce? Is there a required condition?
consistently, every time.
What is the expected behavior? Why is that the expected behavior?
No response
What do you see instead?
My shells no longer seem to be seeing input, and instead standard input seems to be stuck in "echo mode".
zsh recovers after I SIGINT a couple more times. nushell outright gets "stuck" and no new input is visible.
Additional information
I have had trouble capturing information about this. Tools like strace and gdb seem to take stdin and do things, leading to the problem "fixing itself" when being observed directly. I also was having trouble getting gdb to follow spawned children to see what was going on there.
The main thing, though, is that I'm not exactly sure what system call or the like could be causing my problem. I think it might be related to the following ioctl call that was setting ICRNL .... but I could be totally off base (snippet of those calls). I couldn't really reliably reproduce it directly.
565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|ICRNL|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|ICRNL|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|ICRNL|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|ICRNL|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
EDIT: actually I believe the following to be the specific cause, I was able to finally get strace to do its thing
The following happens during teardown (on the second SIGINT, since the first one gets absorbed by the process.once thing).
[pid 697631] ioctl(0, TCGETS,{c_iflag=BRKINT|INLCR|ICRNL|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
[pid 697631] ioctl(0, TCSETS, {c_iflag=BRKINT|ICRNL|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
FOR c_iflag
NO LONGER HERE:
{'INLCR'}
Added:
{'IXON'}
----------------------
FOR c_oflag
NO LONGER HERE:
set()
Added:
set()
----------------------
FOR c_cflag
NO LONGER HERE:
set()
Added:
set()
----------------------
FOR c_lflag
NO LONGER HERE:
set()
Added:
{'ICANON', 'ECHO'}
----------------------
Activity
github-actions commentedon May 24, 2026
This issue has been marked as stale due to 210 days of inactivity.
It will be automatically closed in 30 days if no further activity occurs. If this is still relevant, please leave a comment or update it to keep it open.
github-actions commentedon Jun 24, 2026
This issue has been automatically closed after 30 days of inactivity following its stale status (no activity for a total of 240 days).
If this is still relevant, feel free to reopen it or leave a comment with additional details so we can continue the discussion.