denoland/deno · error · NodeSystemError
ERR_TTY_INIT_FAILED
ERR_TTY_INIT_FAILED
Error message
TTY initialization failed: ${ctx.syscall} returned ${ctx.code} (${ctx.message}) What it means
tty.WriteStream constructs a TTY handle via new TTY(fd, ctx); when the underlying terminal syscall fails (e.g. tcgetattr/termios on an fd that is not a terminal), ctx is filled with syscall/code/message and ERR_TTY_INIT_FAILED is thrown. The fd passed the integer check but is not usable as a terminal.
Source
Thrown at ext/node/polyfills/internal/tty.js:312
// WriteStream needs to be callable without `new` to match Node.js behavior.
function WriteStream(fd) {
ensureWriteStreamPrototype();
if (!ObjectPrototypeIsPrototypeOf(WriteStream.prototype, this)) {
return new WriteStream(fd);
}
if (fd >> 0 !== fd || fd < 0) {
throw new ERR_INVALID_FD(fd);
}
// Non-stdio fds require --allow-all
op_tty_check_fd_permission(fd);
const ctx = {};
const tty = new TTY(fd, ctx);
if (ctx.code !== undefined) {
throw new ERR_TTY_INIT_FAILED(ctx);
}
FunctionPrototypeCall(lazyNet().Socket, this, {
readableHighWaterMark: 0,
handle: tty,
manualStart: true,
});
// Prevents interleaved or dropped stdout/stderr output for terminals.
// As noted in the following reference, local TTYs tend to be quite fast and
// this behavior has become expected due historical functionality on OS X,
// even though it was originally intended to change in v1.0.2 (Libuv 1.2.1).
// Ref: https://github.com/nodejs/node/pull/1771#issuecomment-119351671
this._handle.setBlocking(true);
const winSize = [0, 0];
const err = tty.getWindowSize(winSize);
if (!err) {View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Guard with process.stdout.isTTY / fs.fstatSync(fd).isTTY() before constructing a tty stream
- Fall back to fs.createWriteStream or a plain stream when the fd is not a terminal
- Run the program in a real terminal or under a PTY (script command, node-pty) when TTY behavior is required
Example fix
// before const out = new tty.WriteStream(1); // throws when stdout is piped // after const out = process.stdout.isTTY ? new tty.WriteStream(1) : process.stdout;
Defensive patterns
Strategy: type-guard
Validate before calling
const out = process.stdout.isTTY ? new tty.WriteStream(1) : process.stdout;
Type guard
const isTtyFd = (fd) => {
try { return fs.fstatSync(fd).isTTY(); } catch { return false; }
}; Try / catch
let ws;
try {
ws = new tty.WriteStream(fd);
} catch (err) {
if (err.code === 'ERR_TTY_INIT_FAILED') ws = new lazyNet.Socket({ fd });
else throw err;
} Prevention
- Check isTTY before constructing tty streams
- Gate interactive/tty features behind a flag in CI environments
- Test CLI tools with piped stdout, not only a terminal
When it happens
Trigger: new tty.WriteStream(fd) where fd is open but redirected to a file or pipe; running under CI, cron, or a pipe where stdout/stderr was redirected; constructing a tty.ReadStream/WriteStream on a socket fd.
Common situations: Works on a dev terminal, fails in CI (no TTY allocated); stdout piped into a pager or logger; in Deno, non-stdio fds additionally require --allow-all before the TTY attempt.
Related errors
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/14e9fa629d4f264e.
Report an issue: GitHub.