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
- Check `tty.isatty(fd)` before constructing the ReadStream
- Catch ERR_TTY_INIT_FAILED and degrade to non-interactive output (no colors, no raw mode)
- Allocate a pty in CI when tty behavior must be tested (`script -c` on Linux)
- 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
- Call tty.isatty(fd) before creating tty streams
- Make interactive features optional so piped/CI runs work
- Use a pty wrapper (script -c) when TTY paths must be tested
- Never wrap fds your process has already closed
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.