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
readline with /dev/tty: echos and doesn't close #21319
The use case is that a node script is being run from a piped bash script and so process.stdin is the pipe and /dev/tty must be used explicitly to get user input.
varreadline=require('readline');varinput=require('fs').createReadStream('/dev/tty');varrl=readline.createInterface({input: input,output: process.stdout//, terminal: false});rl.question("This is the prompt: ",function(response){console.log("This is the response:",response);rl.close();// If I don't close the input explicitly, it stays open.input.close();});
Output:
This is the prompt: yay
yay
This is the response: yay
Uncommenting terminal: false solves the issue of echoing, but creates a new problem in that it no longer treats stdout as a proper terminal.
Basically the reader thread is engaged in reading from the device, and is blocked, immune to the stream close. This is closely related to #15439 and a documentation PR detailing the limitation is in progress through #21212
input.close seem to be working in the code you provided but I don't know why it should, as the below code hangs too.
pushing a null character into the device stream helps to unblock the reader thread.
@@ -8,6 +8,7 @@ var rl = readline.createInterface({
rl.question("This is the prompt: ", function (response) {
console.log("This is the response:", response);
rl.close();
+ input.push(null);
// If I don't close the input explicitly, it stays open.
// input.close();
});
To your point, I found that the order in which readline and the input are closed seems to matter:
rl.question("This is the prompt: ",function(response){console.log("This is the response:",response);// If I close the input first, it still stays open.input.close();rl.close();});
It may just be a goldilocks timing issue - or maybe the use of terminal: false changes it too. I haven't tried every permutation.
I'll try your .push(null) and see how that works for me.
@coolaj86 Aside: You would probably rather want to use a TTY stream rather than an fs stream for accessing the TTY? That way you don’t need to worry about blocking reads
@addaleax I'm not quite sure how that works. The docs talk a lot about process.stdin and process.stdout, but they don't actually give any examples of how to use tty.
Based on playing around in the repl I'm guessing I might use it by doing something like this?
@Fishrock123 Yes, that is the correct way to do it on Mac and Linux.
@gireeshpunathil I've tried a few different ways but can't tell if input.push(null) has any affect.
It seems like node is now properly exiting, but leaving /dev/tty in a bad state. The shell will just hang until I hit enter and then the bash script that was calling the node script continues.
For now I just print "Press any key to continue..." before calling process.exit(0) and then once enter is hit everything goes as expected.
It seems like node is now properly exiting, but leaving /dev/tty in a bad state. The shell will just hang until I hit enter and then the bash script that was calling the node script continues.
You didn't mention how exactly you (or bash) start node, but if it's running in the background, then opening /dev/tty without the fs.constants.O_NOCTTY flag turns it into a controlling terminal. That's unlikely to be what you want.
I added fs.constants.O_NOCTTY and it did not change the behavior.
For context
I made a convenient curl | bash installer to bootstrap the install process of my app:
curl -fsSL https://get.example.app | bash
When that script runs it installs an exact version of node (v10.2.1 presently as other versions we tested had particular bugs that broke the app) to a special prefix directory and (unzip / tar / gunzip)s the an exact version (tag) of the app from a git repo also into that same directory.
installer.sh:
#!/bin/bash# do some stuff to install node# do some stuff to unzip / untar our app# generate a bash file to call the app
/path/to/app/bin/app --init --some options
/path/to/app/bin/app
The script generates and then calls another script which calls that version of node and the app, passing any options.
# This is where it needs some additional input from the user to finish the install
/path/to/app/bin/app --init --some options
# That hangs and it doesn't progress to here:
/path/to/app/bin/app
We're now talking about 2 separate but related issues. I'll try to come up with a test case to show this second issue as well.
Hi, @coolaj86. I know this issue has been quiet for a very long time and it's possible you're not working on the relevant code base anymore. Which of the issues described here are you still experiencing (if any)?
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.
This issue has been automatically closed after 30 days of inactivity following its stale status (no activity for a total of 120 days).
If this is still relevant, feel free to reopen it or leave a comment with additional details so we can continue the discussion.
Activity
gireeshpunathil commentedon Jun 14, 2018
Basically the reader thread is engaged in reading from the device, and is blocked, immune to the stream close. This is closely related to #15439 and a documentation PR detailing the limitation is in progress through #21212
input.closeseem to be working in the code you provided but I don't know why it should, as the below code hangs too.pushing a null character into the device stream helps to unblock the reader thread.
Hope this helps.
coolaj86 commentedon Jun 14, 2018
Thanks.
To your point, I found that the order in which readline and the input are closed seems to matter:
It may just be a goldilocks timing issue - or maybe the use of
terminal: falsechanges it too. I haven't tried every permutation.I'll try your
.push(null)and see how that works for me.addaleax commentedon Jun 14, 2018
@coolaj86 Aside: You would probably rather want to use a
TTYstream rather than anfsstream for accessing the TTY? That way you don’t need to worry about blocking readscoolaj86 commentedon Jun 14, 2018
@addaleax I'm not quite sure how that works. The docs talk a lot about
process.stdinandprocess.stdout, but they don't actually give any examples of how to usetty.Based on playing around in the repl I'm guessing I might use it by doing something like this?
Could I somehow use
ttyon Windows too?addaleax commentedon Jun 14, 2018
@coolaj86 Yes, exactly. :)
I don’t know about that. @nodejs/platform-windows
bzoz commentedon Jun 14, 2018
I guess
fs.openSync('conin$', 'r')would be something similar on Windows, but it fails withENOENTFishrock123 commentedon Jun 15, 2018
This code seems correct to me, did that help @coolaj86?
So long as you know the stdin handle, I think so.
coolaj86 commentedon Jun 24, 2018
@Fishrock123 Yes, that is the correct way to do it on Mac and Linux.
@gireeshpunathil I've tried a few different ways but can't tell if
input.push(null)has any affect.It seems like node is now properly exiting, but leaving
/dev/ttyin a bad state. The shell will just hang until I hit enter and then the bash script that was calling the node script continues.For now I just print "Press any key to continue..." before calling
process.exit(0)and then once enter is hit everything goes as expected.bnoordhuis commentedon Jun 24, 2018
You didn't mention how exactly you (or bash) start node, but if it's running in the background, then opening
/dev/ttywithout thefs.constants.O_NOCTTYflag turns it into a controlling terminal. That's unlikely to be what you want.coolaj86 commentedon Jun 28, 2018
I added
fs.constants.O_NOCTTYand it did not change the behavior.For context
I made a convenient curl | bash installer to bootstrap the install process of my app:
curl -fsSL https://get.example.app | bashWhen that script runs it installs an exact version of node (v10.2.1 presently as other versions we tested had particular bugs that broke the app) to a special prefix directory and (unzip / tar / gunzip)s the an exact version (tag) of the app from a git repo also into that same directory.
installer.sh:The script generates and then calls another script which calls that version of node and the app, passing any options.
/path/to/bin/app:Where it gets stuck
We're now talking about 2 separate but related issues. I'll try to come up with a test case to show this second issue as well.
Trott commentedon Mar 15, 2021
Hi, @coolaj86. I know this issue has been quiet for a very long time and it's possible you're not working on the relevant code base anymore. Which of the issues described here are you still experiencing (if any)?
github-actions commentedon Jun 27, 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 Jul 28, 2026
This issue has been automatically closed after 30 days of inactivity following its stale status (no activity for a total of 120 days).
If this is still relevant, feel free to reopen it or leave a comment with additional details so we can continue the discussion.