denoland/deno · error · NodeRangeError

ERR_INVALID_FD

ERR_INVALID_FD

Error message

"fd" must be a positive integer: ${fd}

What it means

tty.WriteStream validates its fd with the 32-bit check 'fd >> 0 !== fd || fd < 0', accepting only non-negative integers representable in int32. Negative numbers, fractions, NaN, strings, and values above 2^31-1 throw ERR_INVALID_FD before any syscall is attempted.

Source

Thrown at ext/node/polyfills/internal/tty.js:303

}

function removeSigwinchListener(stream) {
  SetPrototypeDelete(sigwinchStreams, stream);
  if (SetPrototypeGetSize(sigwinchStreams) === 0 && sigwinchRegistered) {
    sigwinchRegistered = false;
    Deno.removeSignalListener("SIGWINCH", onSigwinch);
  }
}

// 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.

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Pass a real numeric fd: 0, 1, 2, or one returned by fs.openSync()
  2. Coerce and check: const fd = Number(input); if (!Number.isInteger(fd) || fd < 0) throw ...
  3. Use null plus an explicit branch instead of -1 as a 'no fd' sentinel

Example fix

// before
const ws = new tty.WriteStream(process.env.OUT_FD); // string '1' -> throws

// after
const fd = Number(process.env.OUT_FD);
if (!Number.isInteger(fd) || fd < 0) throw new Error(`Invalid fd: ${process.env.OUT_FD}`);
const ws = new tty.WriteStream(fd);
Defensive patterns

Strategy: type-guard

Validate before calling

const fd = Number(rawFd);
if (!Number.isInteger(fd) || fd < 0 || fd > 0x7fffffff) {
  throw new Error(`Invalid fd: ${rawFd}`);
}
const ws = new tty.WriteStream(fd);

Type guard

const isValidFd = (fd) => Number.isInteger(fd) && fd >= 0 && fd <= 0x7fffffff;

Prevention

When it happens

Trigger: new tty.WriteStream(-1); new tty.WriteStream(1.5); new tty.WriteStream(process.env.OUT_FD) where the value is a string like '1'; a computed fd that turns out NaN.

Common situations: Reading fd numbers from env vars/CLI flags as strings; -1 sentinels from other APIs; fds handed back by native layers that use -1 for errors.

Related errors


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