You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Node doesn't reset tty on early/aborted exit #41143
Based on a bit of strace investigation, I think the issue isn't that node doesn't reset the tty state, it's that it does (and shouldn't). Here's what I think is happening:
less starts, saves tty state, sets -echo
node starts, saves tty state (for some reason?)
less exits, restores +echo
node gets SIGPIPE, exits, restores "original" tty state of -echo
node should probably not touch the tty state at all in this sort of situation where it's just being used to print to stdout.
Seems like isatty() can/should be called (if its not already) to determine if stdout is a terminal or a file/pipe...
I can confirm that if I redirect stdin and stderr to/from /dev/null, the problem goes away. If I redirect either one (but not both) the problem persists. It looks like node is checking to see if any one of the three (stdin, stdout, stderr) is a terminal. If any one of the three is, it stores/restores the tty. I'm not sure what the correct logic is, but probably not that.
Sounds like in a pipeline, there is a foreground process (less in this case) that is "in charge" of dealing with the terminal. I wonder if there is some more meaningful measure than isatty()? Or may it is as simple as testing stdout and ignoring what stdin and stderr are doing?
I think the right thing is to just not touch the tty state at all if the program doesn't request it. That's what most programs do by default and it behaves correctly.
From a quick search of the code, my guess is that it's happening here: https://github.com/nodejs/node/blob/master/lib/repl.js#L547-L591
If the REPL evaluation code is being used to run scripts, then it's saving and restoring the terminal mode as if it was being used interactively. But there's no reason for it to do that for non-interactive use.
I like shachaf's opinion of leaving the tty alone unless node is explicitly launched in interactive mode (and therefore not buried in a pipeline somewhere). If you're not running some kind of command-line input, there's probably no reason to diddle the tty settings.
If an add-on wants to change tty settings, it should probably register its own exit hook (assuming that can be done) to restore settings. But it doesn't seem to me like node should always assume the tty needs to be restored just because some add-on might have done something to it.
Anything that requires the ecosystem to move is pretty much DOA.
The yubikey openssl engine (provides TLS keys to node) is a good example of why that isn't workable. It leaves the tty in a bad state if you press ^C on the passphrase prompt and an exit hook won't fix that because node and the yubikey are unaware of each other's existence.
(And let's not get into the perils of cleaning up from signal handlers!)
Basically, if you want to see this fixed, work within the existing framework. Happy to review pull requests, just cc me. :)
Compatibility with existing programs seems important. I think an opt-in fix
(e.g. setting a flag to tell ResetStdio to leave the terminal as-is, which
you could set if your program isn't supposed to affect terminal state)
would be fine -- right now the bug is just unavoidable.
I found this issue after noticing weird behavior when doing node index.js | less-like-app. When the node process exits, the interactivity of the less-like application breaks because node messes with the tty state.
Sounds like in a pipeline, there is a foreground process (less in this case) that is "in charge" of dealing with the terminal. I wonder if there is some more meaningful measure than isatty()? Or may it is as simple as testing stdout and ignoring what stdin and stderr are doing?
This sounds like a reasonable fix to me. @bnoordhuis do you think that would be acceptable?
Anything that requires the ecosystem to move is pretty much DOA.
Would it be an acceptable to disable this behaviour using a new option in NODE_OPTIONS - either to disable the tty manipulation completely or only if stdout is not a tty?
Then users who use the command line can opt out by setting an environment variable and node-based command line tools could consider opening with eg #!/usr/bin/env node --no-stdio-init-on-redirect ?
Just stumbled upon this thread after spending a whole day debugging what should have been a really simple task and I'm amazed that 4 years on this is still here with barely any activity. Still present in both 24 and 26.
Can anyone provide a solid workaround for this? I'm trying setRawMode(false) on process exit but that doesn't fix it.
I'm trying to create a simple transform script from stdin to stdout regardless of whether either (or both) streams are pipes or tty.
This issue has been marked as stale due to 90 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.
This still occurs and still has the same severity, even if the team doesn't prioritize it.
The last time I had to deal with this my workaround was moving to Deno.
Activity
shachaf commentedon Mar 14, 2022
Based on a bit of strace investigation, I think the issue isn't that node doesn't reset the tty state, it's that it does (and shouldn't). Here's what I think is happening:
node should probably not touch the tty state at all in this sort of situation where it's just being used to print to stdout.
kjeib commentedon Mar 14, 2022
Seems like isatty() can/should be called (if its not already) to determine if stdout is a terminal or a file/pipe...
I can confirm that if I redirect stdin and stderr to/from /dev/null, the problem goes away. If I redirect either one (but not both) the problem persists. It looks like node is checking to see if any one of the three (stdin, stdout, stderr) is a terminal. If any one of the three is, it stores/restores the tty. I'm not sure what the correct logic is, but probably not that.
Here's an article with a bit of guidance about 20% in (search for "pipeline"):
https://www.linusakesson.net/programming/tty/
Sounds like in a pipeline, there is a foreground process (less in this case) that is "in charge" of dealing with the terminal. I wonder if there is some more meaningful measure than isatty()? Or may it is as simple as testing stdout and ignoring what stdin and stderr are doing?
shachaf commentedon Mar 15, 2022
I think the right thing is to just not touch the tty state at all if the program doesn't request it. That's what most programs do by default and it behaves correctly.
From a quick search of the code, my guess is that it's happening here: https://github.com/nodejs/node/blob/master/lib/repl.js#L547-L591
If the REPL evaluation code is being used to run scripts, then it's saving and restoring the terminal mode as if it was being used interactively. But there's no reason for it to do that for non-interactive use.
bnoordhuis commentedon Mar 15, 2022
For anyone who wants to work on this, the relevant logic is in src/node.cc:
node restores the tty's state most of the time, see ResetStdio (called on normal program exit and SIGINT or SIGTERM but not other signals)
the state is captured at startup in PlatformInit
Node has to work this way because native add-ons can also change the tty's state.
kjeib commentedon Mar 15, 2022
I like shachaf's opinion of leaving the tty alone unless node is explicitly launched in interactive mode (and therefore not buried in a pipeline somewhere). If you're not running some kind of command-line input, there's probably no reason to diddle the tty settings.
If an add-on wants to change tty settings, it should probably register its own exit hook (assuming that can be done) to restore settings. But it doesn't seem to me like node should always assume the tty needs to be restored just because some add-on might have done something to it.
bnoordhuis commentedon Mar 15, 2022
Anything that requires the ecosystem to move is pretty much DOA.
The yubikey openssl engine (provides TLS keys to node) is a good example of why that isn't workable. It leaves the tty in a bad state if you press ^C on the passphrase prompt and an exit hook won't fix that because node and the yubikey are unaware of each other's existence.
(And let's not get into the perils of cleaning up from signal handlers!)
Basically, if you want to see this fixed, work within the existing framework. Happy to review pull requests, just cc me. :)
shachaf commentedon Mar 15, 2022
brendon commentedon Oct 7, 2023
It could be a coincidence but updating to Node 18 fixed this for me.
JosephHalter commentedon May 7, 2024
I just want to confirm that it's still happening on node 20.12.1, running
yarn run serveand Ctrl+C messes up my terminal every time.brendon commentedon May 7, 2024
Turns out I was too hasty. It still occurs in 18. Haven't checked 20.
kjeib commentedon May 7, 2024
I just checked with 20.13.0 and the bug is still present.
deadbeef84 commentedon Jan 20, 2025
I found this issue after noticing weird behavior when doing
node index.js | less-like-app. When the node process exits, the interactivity of the less-like application breaks because node messes with the tty state.This sounds like a reasonable fix to me. @bnoordhuis do you think that would be acceptable?
unilynx commentedon Sep 9, 2025
Would it be an acceptable to disable this behaviour using a new option in NODE_OPTIONS - either to disable the tty manipulation completely or only if stdout is not a tty?
Then users who use the command line can opt out by setting an environment variable and node-based command line tools could consider opening with eg
#!/usr/bin/env node --no-stdio-init-on-redirect?bteixeira commentedon Jun 2, 2026
Just stumbled upon this thread after spending a whole day debugging what should have been a really simple task and I'm amazed that 4 years on this is still here with barely any activity. Still present in both 24 and 26.
Can anyone provide a solid workaround for this? I'm trying
setRawMode(false)on process exit but that doesn't fix it.I'm trying to create a simple transform script from stdin to stdout regardless of whether either (or both) streams are pipes or tty.
github-actions commentedon Sep 1, 2026
This issue has been marked as stale due to 90 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.
brendon commentedon Sep 1, 2026
Probably not stale?
bteixeira commentedon Sep 3, 2026
This still occurs and still has the same severity, even if the team doesn't prioritize it.
The last time I had to deal with this my workaround was moving to Deno.