denoland/deno · error · RangeError
ERR_INVALID_FD
ERR_INVALID_FD
Error message
"fd" must be a positive integer: ${fd} What it means
The legacy tty.ReadStream constructor validates fd before anything else: fd must satisfy `fd >> 0 === fd && fd >= 0`, i.e. a non-negative int32 integer. Negative numbers, fractions, NaN, strings, BigInts, or values above 2147483647 throw ERR_INVALID_FD; fd 0 (stdin) is accepted despite the 'positive integer' message wording. After this check, non-stdio fds additionally require --allow-all permission in Deno.
Source
Thrown at ext/node/polyfills/tty_esm.ts:31
} = primordials;
const {
ERR_INVALID_FD,
ERR_TTY_INIT_FAILED,
} = core.loadExtScript("ext:deno_node/internal/errors.ts");
const { isatty } = core.loadExtScript("ext:deno_node/tty.js");
// ReadStream needs to be callable without `new` to match Node.js behavior.
// We use a wrapper function that delegates to the actual class.
// 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),
});View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Convert with Number() and validate: `Number.isInteger(fd) && fd >= 0 && fd <= 0x7fffffff`
- Use process.stdin/stdout/stderr or the literal fds 0, 1, 2 instead of raw numbers
- Grant --allow-all when wrapping fds beyond stdio in Deno (op_tty_check_fd_permission)
- Prefer tty.isatty(fd) checks plus high-level stream APIs over constructing ReadStream directly
Example fix
// before
const fd = process.env.FD; // string from env
new tty.ReadStream(fd); // ERR_INVALID_FD
// after
const fd = Number(process.env.FD);
if (!Number.isInteger(fd) || fd < 0 || fd > 0x7fffffff) {
throw new RangeError(`bad fd: ${process.env.FD}`);
}
new tty.ReadStream(fd); Defensive patterns
Strategy: type-guard
Validate before calling
const fd = Number(rawFd);
if (!Number.isInteger(fd) || fd < 0 || fd > 0x7fffffff) {
throw new Error(`fd must be a non-negative int32, got ${String(rawFd)}`);
}
const rs = new tty.ReadStream(fd); Type guard
const isValidFd = (fd: unknown): fd is number => typeof fd === "number" && Number.isInteger(fd) && fd >= 0 && fd <= 0x7fffffff;
Try / catch
try {
stream = new tty.ReadStream(fd);
} catch (e: any) {
if (e?.code === "ERR_INVALID_FD") {
throw new Error(`invalid fd from configuration: ${String(fd)}`);
}
throw e;
} Prevention
- Convert fd strings from argv/env with Number() and validate before use
- Prefer process.stdin/stdout objects over numeric fds
- Remember non-stdio fds need --allow-all under Deno
When it happens
Trigger: `new tty.ReadStream(process.argv[2])` with an unconverted string; a fractional fd like 1.5; a negative sentinel like -1; `parseInt(x)` returning NaN; fds above 2^31-1 obtained from another API.
Common situations: Reading fd numbers from environment variables or CLI args without Number(); fd variables that are undefined after a failed lookup; code ported from runtimes with different fd validation rules.
Related errors
- ERR_INVALID_FD
- invalid ${name}, must not be infinity or NaN
- ERR_INVALID_HTTP_TOKEN
- ERR_INVALID_HTTP_TOKEN
- ERR_HTTP_INVALID_HEADER_VALUE
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/7064ef2cba439f6b.
Report an issue: GitHub.