{"record":{"id":"14e9fa629d4f264e","repo":"denoland/deno","slug":"err-tty-init-failed","errorCode":"ERR_TTY_INIT_FAILED","errorMessage":"TTY initialization failed: ${ctx.syscall} returned ${ctx.code} (${ctx.message})","messagePattern":"TTY initialization failed: (.+?) returned (.+?) \\((.+?)\\)","errorType":"exception","errorClass":"NodeSystemError","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/internal/tty.js","lineNumber":312,"sourceCode":"\n// WriteStream needs to be callable without `new` to match Node.js behavior.\nfunction WriteStream(fd) {\n  ensureWriteStreamPrototype();\n  if (!ObjectPrototypeIsPrototypeOf(WriteStream.prototype, this)) {\n    return new WriteStream(fd);\n  }\n\n  if (fd >> 0 !== fd || fd < 0) {\n    throw new ERR_INVALID_FD(fd);\n  }\n\n  // Non-stdio fds require --allow-all\n  op_tty_check_fd_permission(fd);\n\n  const ctx = {};\n  const tty = new TTY(fd, ctx);\n  if (ctx.code !== undefined) {\n    throw new ERR_TTY_INIT_FAILED(ctx);\n  }\n\n  FunctionPrototypeCall(lazyNet().Socket, this, {\n    readableHighWaterMark: 0,\n    handle: tty,\n    manualStart: true,\n  });\n\n  // Prevents interleaved or dropped stdout/stderr output for terminals.\n  // As noted in the following reference, local TTYs tend to be quite fast and\n  // this behavior has become expected due historical functionality on OS X,\n  // even though it was originally intended to change in v1.0.2 (Libuv 1.2.1).\n  // Ref: https://github.com/nodejs/node/pull/1771#issuecomment-119351671\n  this._handle.setBlocking(true);\n\n  const winSize = [0, 0];\n  const err = tty.getWindowSize(winSize);\n  if (!err) {","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/internal/tty.js#L294-L330","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst out = new tty.WriteStream(1); // throws when stdout is piped\n\n// after\nconst out = process.stdout.isTTY ? new tty.WriteStream(1) : process.stdout;","handlingStrategy":"type-guard","validationCode":"const out = process.stdout.isTTY ? new tty.WriteStream(1) : process.stdout;","typeGuard":"const isTtyFd = (fd) => {\n  try { return fs.fstatSync(fd).isTTY(); } catch { return false; }\n};","tryCatchPattern":"let ws;\ntry {\n  ws = new tty.WriteStream(fd);\n} catch (err) {\n  if (err.code === 'ERR_TTY_INIT_FAILED') ws = new lazyNet.Socket({ fd });\n  else throw err;\n}","preventionTips":["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"],"tags":["tty","terminal","node-compat","deno"],"backgroundTag":"not-a-tty","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-26T04:18:47.238Z"}