denoland/deno · error · NodeTypeError
ERR_INVALID_HANDLE_TYPE
ERR_INVALID_HANDLE_TYPE
Error message
This handle type cannot be sent
What it means
When a net.Socket is sent over IPC (child.send(message, socket)), the polyfill extracts a raw file descriptor the receiving process can re-open. rawFdFromTcpHandle throws ERR_INVALID_HANDLE_TYPE when the TCP handle's fdForIpc() returns a negative value — the socket exists but has no fd shareable across processes. Node applies the same rule: sockets whose descriptor cannot be duplicated are rejected.
Source
Thrown at ext/node/polyfills/internal/child_process.ts:2279
if (!socketLists) {
socketLists = new SafeWeakMap();
socketListsByChild.set(child, socketLists);
}
let socketList = socketLists.get(server);
if (!socketList) {
socketList = new SocketListSend(child, server);
socketLists.set(server, socketList);
}
return socketList;
}
function rawFdFromTcpHandle(tcpHandle) {
if (typeof tcpHandle.fdForIpc !== "function") {
notImplemented("ChildProcess.send with handle on this platform");
}
const rawFd = tcpHandle.fdForIpc();
if (rawFd < 0) {
throw new ERR_INVALID_HANDLE_TYPE();
}
return rawFd;
}
function getIpcHandleInfo(handle, options, target) {
const { Socket } = lazyNet();
const { Server: NetServer } = lazyNet();
const { Socket: DgramSocket } = lazyDgram();
if (ObjectPrototypeIsPrototypeOf(Socket.prototype, handle)) {
const inner = handle._handle;
// Match Node's handleConversion["net.Socket"].send, which returns the
// socket's native handle. A socket without an underlying handle (e.g.
// already destroyed) yields null; Node then strips the handle and sends
// the message alone instead of throwing.
if (!inner) {
return null;
}
const isTcp = ObjectPrototypeIsPrototypeOf(TCP.prototype, inner);View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Ensure the socket is fully connected and not destroyed before send()
- Do not send TLS/HTTPS sockets; send the underlying plain net.Socket or connection metadata
- On failure, fall back to sending { host, port } and let the child reconnect itself
- Wrap send() in try-catch keyed on err.code === "ERR_INVALID_HANDLE_TYPE" and degrade gracefully
Example fix
// before
const sock = tls.connect(443, host);
sock.on("secureConnect", () => child.send("conn", sock)); // TLS socket not shareable
// after
const sock = net.connect(80, host);
sock.on("connect", () => {
try {
child.send("conn", sock);
} catch (err) {
if (err.code === "ERR_INVALID_HANDLE_TYPE") {
child.send("conn-info", { host, port: 80 });
} else throw err;
}
}); Defensive patterns
Strategy: try-catch
Validate before calling
if (!(socket instanceof net.Socket) || socket.destroyed || socket.pending) {
throw new Error("socket is not in a shareable state for IPC");
} Type guard
const isShareableTcpSocket = (s) => s instanceof net.Socket && !s.destroyed && !s.pending;
Try / catch
try {
child.send(msg, socket);
} catch (err) {
if (err.code === "ERR_INVALID_HANDLE_TYPE") {
child.send(msg, { host, port }); // fall back to reconnect-style handoff
} else {
throw err;
}
} Prevention
- Only send freshly connected, non-destroyed net.Socket handles
- Never send TLS/HTTPS sockets; send the underlying socket before upgrade, or metadata
- Treat ERR_INVALID_HANDLE_TYPE as a signal to fall back to a metadata handoff
When it happens
Trigger: child.send("go", socket) where socket is a net.Socket whose fdForIpc() returns < 0 (closed, not yet connected, or otherwise unshareable); forwarding handles between cluster workers in the wrong lifecycle state.
Common situations: Sending TLS-wrapped or already-destroyed sockets; custom IPC proxies that forward handles before the connection settles; platforms where fd sharing is limited (the adjacent notImplemented path fires where fdForIpc is unavailable).
Related errors
- ERR_IPC_DISCONNECTED
- ipc stream closed while reading message
- ipc stream closed before message length
- ERR_CHILD_PROCESS_IPC_REQUIRED
- ERR_IPC_ONE_PIPE
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/ac132935a0254075.
Report an issue: GitHub.