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

  1. Guard with process.stdout.isTTY / fs.fstatSync(fd).isTTY() before constructing a tty stream
  2. Fall back to fs.createWriteStream or a plain stream when the fd is not a terminal
  3. 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

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.