denoland/deno · error · TypeError
ERR_INVALID_FD_TYPE
ERR_INVALID_FD_TYPE
Error message
Unsupported fd type: ${type} What it means
When a net Socket or Server is created from a raw file descriptor (new net.Socket({ fd }) or server.listen({ fd })), _createHandle asks the OS-level guessHandleType and only supports 'PIPE' and 'TCP'. Any other fd type — TTY (terminal), FILE (regular file, /dev/null), UDP — throws ERR_INVALID_FD_TYPE with the guessed type name.
Source
Thrown at ext/node/polyfills/net.ts:436
function _isPipeName(s: unknown): s is string {
return typeof s === "string" && _toNumber(s) === false;
}
function _createHandle(fd: number, isServer: boolean): Handle {
validateInt32(fd, "fd", 0);
const type = guessHandleType(fd);
if (type === "PIPE") {
return new Pipe(isServer ? PipeConstants.SERVER : PipeConstants.SOCKET);
}
if (type === "TCP") {
return new TCP(isServer ? TCPConstants.SERVER : TCPConstants.SOCKET);
}
throw new ERR_INVALID_FD_TYPE(type);
}
// Returns an array [options, cb], where options is an object,
// cb is either a function or null.
// Used to normalize arguments of `Socket.prototype.connect()` and
// `Server.prototype.listen()`. Possible combinations of parameters:
// - (options[...][, cb])
// - (path[...][, cb])
// - ([port][, host][...][, cb])
// For `Socket.prototype.connect()`, the [...] part is ignored
// For `Server.prototype.listen()`, the [...] part is [, backlog]
// but will not be handled here (handled in listen())
function _normalizeArgs(args: unknown[]): NormalizedArgs {
let arr: NormalizedArgs;
if (args.length === 0) {
arr = [{}, null];
arr[normalizedArgsSymbol] = true;View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Only pass fds that are actual sockets (TCP) or pipes — e.g. from socketpair, pipe(2), or a real activated socket
- Guard interactive/dev runs: skip the fd path unless the fd is known to be a socket (in dev, listen on a port instead)
- For dgram fds use node:dgram APIs, not net
Example fix
// before
server.listen({ fd: Number(process.env.LISTEN_FDS) ? 3 : 0 }); // fd 0 is a TTY in dev
// after
const fd = Number(process.env.LISTEN_FDS) > 0 ? 3 : undefined;
if (fd !== undefined) server.listen({ fd });
else server.listen(3000); Defensive patterns
Strategy: try-catch
Validate before calling
const isPosixSocketLike = (fd: number) => {
try {
const st = Deno.fstatSync(fd as any); // Deno-only probe
return st.mode != null && (st.mode & 0o140000) === 0o140000; // S_IFSOCK
} catch { return false; }
}; Try / catch
try {
server.listen({ fd });
} catch (e: any) {
if (e?.code === 'ERR_INVALID_FD_TYPE') server.listen(3000); // dev fallback: plain port
else throw e;
} Prevention
- Only hand socket/pipe fds to net (socketpair, pipe(2), systemd-activated sockets)
- In dev, branch on the presence of the activation env (LISTEN_FDS) and listen on a port otherwise
- Remember stdin/stdout type depends on how the process was launched (TTY vs pipe vs file)
When it happens
Trigger: server.listen({ fd: 0 }) while stdin is a terminal (type TTY); passing the fd of a regular file or /dev/null; wrapping a UDP socket fd from dgram into net; systemd-style fd handoff where the fd is not a socket/pipe.
Common situations: Systemd socket-activation code tested interactively (fd 0 becomes a TTY instead of the activated socket); scripts that try to serve over stdout redirected to a file; cluster fd-passing patterns on unsupported handle kinds.
Related errors
- TLS wrap attach failed: ${attachResult}
- TLS wrap attach (JS stream) failed: ${attachResult}
- ERR_INVALID_ARG_TYPE
- Expected data to be a string, Buffer, Uint8Array, or ArrayBu
- ERR_INVALID_IP_ADDRESS
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/e7ad5c6acf790306.
Report an issue: GitHub.