Version
v24.11.0
Platform
Microsoft Windows NT 10.0.26200.0 x64
Subsystem
node:tty or node:fs
What steps will reproduce the bug?
Run the following code in a CommonJS setting (or convert requires to imports):
const fs = require("node:fs");
const tty = require("node:tty");
const fd = fs.openSync("\\\\.\\CONIN$", "r");
const input = new tty.ReadStream(fd);
input.setRawMode(true);
How often does it reproduce? Is there a required condition?
Windows
What is the expected behavior? Why is that the expected behavior?
Successful open of console/TTY in raw mode
What do you see instead?
\\.\CONIN$ opens and new tty.ReadStream(fd) reports isTTY: true, but setRawMode(true) throws EPERM.
Additional information
Motivation
A terminal TUI/pager such as hunk needs to read application data from stdin, while still interacting via keyboard/mouse input from the controlling console. For example:
In this process, fd 0 is the pipe containing the diff, not the keyboard. On Unix, this can be handled by opening /dev/tty and passing that fd to new tty.ReadStream(fd), then calling setRawMode(true). On Windows, I expected \\.\CONIN$ to be the equivalent, and Node can open it and reports the resulting tty.ReadStream as a TTY, but setRawMode(true) fails with EPERM.
I also tried a few other names for CONIN$:
const fs = require("node:fs");
const tty = require("node:tty");
for (const path of ["CON", "CONIN$", "\\\\.\\CONIN$", "//./CONIN$"]) {
try {
const fd = fs.openSync(path, "r");
const stream = new tty.ReadStream(fd);
let result;
try {
stream.setRawMode(true);
result = "raw ok";
stream.setRawMode(false);
} catch (error) {
result = `raw failed: ${error.code} ${error.message}`;
}
console.error(path, {
fd,
isTTY: stream.isTTY,
result,
});
stream.destroy();
} catch (error) {
console.error(path, "open failed:", error.code, error.message);
}
}
Output:
CON open failed: ENOENT ENOENT: no such file or directory, open '...\CON'
CONIN$ open failed: ENOENT ENOENT: no such file or directory, open '...\CONIN$'
\\.\CONIN$ { fd: 3, isTTY: true, result: 'raw failed: EPERM setRawMode EPERM' }
//./CONIN$ { fd: 3, isTTY: true, result: 'raw failed: EPERM setRawMode EPERM' }
There should be a supported way for a Windows CLI/TUI application to reopen the controlling console input while stdin is a pipe, then enable raw mode on that input stream. Otherwise, tools that consume piped stdin but need interactive terminal input cannot work as full-screen TUIs on Windows without native addons or platform-specific external helpers.
Version
v24.11.0
Platform
Subsystem
node:tty or node:fs
What steps will reproduce the bug?
Run the following code in a CommonJS setting (or convert
requires toimports):How often does it reproduce? Is there a required condition?
Windows
What is the expected behavior? Why is that the expected behavior?
Successful open of console/TTY in raw mode
What do you see instead?
\\.\CONIN$opens andnew tty.ReadStream(fd)reportsisTTY: true, butsetRawMode(true)throwsEPERM.Additional information
Motivation
A terminal TUI/pager such as hunk needs to read application data from stdin, while still interacting via keyboard/mouse input from the controlling console. For example:
git diff | hunk pager -In this process, fd 0 is the pipe containing the diff, not the keyboard. On Unix, this can be handled by opening
/dev/ttyand passing that fd tonew tty.ReadStream(fd), then callingsetRawMode(true). On Windows, I expected\\.\CONIN$to be the equivalent, and Node can open it and reports the resultingtty.ReadStreamas a TTY, butsetRawMode(true)fails withEPERM.I also tried a few other names for
CONIN$:Output:
There should be a supported way for a Windows CLI/TUI application to reopen the controlling console input while stdin is a pipe, then enable raw mode on that input stream. Otherwise, tools that consume piped stdin but need interactive terminal input cannot work as full-screen TUIs on Windows without native addons or platform-specific external helpers.