denoland/deno · error · SystemError

ERR_TTY_INIT_FAILED

ERR_TTY_INIT_FAILED

Error message

TTY initialization failed: ${ctx.syscall} returned ${ctx.code} (${ctx.message})

What it means

tty.ReadStream creation wraps a native TTY operation; if that syscall fails, the error context (syscall name, errno, OS message) is wrapped in ERR_TTY_INIT_FAILED — e.g. an ioctl like TCGETS returning ENOTTY when the fd is a pipe rather than a terminal, or EBADF for a closed fd. The message reports syscall, code and OS message so the failing operation is identifiable.

Source

Thrown at ext/node/polyfills/tty_esm.ts:41

// deno-lint-ignore no-explicit-any
function ReadStream(this: any, fd: number, options?: unknown) {
  if (!ObjectPrototypeIsPrototypeOf(ReadStream.prototype, this)) {
    // deno-lint-ignore no-explicit-any
    return new (ReadStream as any)(fd, options);
  }

  if (fd >> 0 !== fd || fd < 0) {
    throw new ERR_INVALID_FD(fd);
  }

  // Non-stdio fds require --allow-all
  op_tty_check_fd_permission(fd);

  // deno-lint-ignore no-explicit-any
  const ctx: any = {};
  const tty = new TTY(fd, ctx);
  if (ctx.code !== undefined) {
    throw new ERR_TTY_INIT_FAILED(ctx);
  }
  FunctionPrototypeCall(Socket, this, {
    readableHighWaterMark: 0,
    handle: tty,
    manualStart: true,
    // deno-lint-ignore no-explicit-any
    ...(options as any),
  });

  this.isRaw = false;
  this.isTTY = true;
}

ObjectSetPrototypeOf(ReadStream.prototype, Socket.prototype);
ObjectSetPrototypeOf(ReadStream, Socket);

// deno-lint-ignore no-explicit-any
(ReadStream as any).prototype.setRawMode = function setRawMode(

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Check `tty.isatty(fd)` before constructing the ReadStream
  2. Catch ERR_TTY_INIT_FAILED and degrade to non-interactive output (no colors, no raw mode)
  3. Allocate a pty in CI when tty behavior must be tested (`script -c` on Linux)
  4. Do not construct ReadStream around fds your code has already closed

Example fix

// before
const rs = new tty.ReadStream(fd); // throws when fd is a pipe (CI, redirects)

// after
if (tty.isatty(fd)) {
  const rs = new tty.ReadStream(fd);
} else {
  // not a terminal: keep plain stream behavior
  process.stdout.write("output (no tty)\n");
}
Defensive patterns

Strategy: validation

Validate before calling

if (!tty.isatty(fd)) {
  // pipe or file: skip TTY setup, use plain stream behavior
  plainMode = true;
} else {
  const rs = new tty.ReadStream(fd);
}

Type guard

const isTerminal = (fd: number): boolean => {
  try {
    return tty.isatty(fd);
  } catch {
    return false;
  }
};

Try / catch

try {
  const rs = new tty.ReadStream(fd);
} catch (e: any) {
  if (e?.code === "ERR_TTY_INIT_FAILED") {
    plainMode = true; // degrade to non-interactive output
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: `new tty.ReadStream(1)` while stdout is piped (`app | cat`, CI logs, redirects); wrapping an fd that was already closed; opening /dev/null or a socket and treating that fd as a tty.

Common situations: Color/interactive CLI code run under CI or cron without a terminal; Docker/Kubernetes containers started without tty allocation; scripts that worked on a dev terminal but fail when output is redirected.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/54fdeba27ea1f0d0. Report an issue: GitHub.