denoland/deno · error · NodeTypeError
ERR_INVALID_ARG_TYPE
ERR_INVALID_ARG_TYPE
Error message
The "options.fd" property must be of type number or an instance of FileHandle. Received ${actual} What it means
Reached at the end of fd validation in the stream constructors: options.fd was neither a number nor an object whose prototype chain includes FileHandle.prototype. Streams accept only an integer fd or a real FileHandle, so strings, BigInts, plain objects and Promises are rejected with ERR_INVALID_ARG_TYPE listing the two accepted types.
Source
Thrown at ext/node/polyfills/internal/fs/streams.mjs:191
stream[kFs] = options.fs || lazyFs();
return options.fd;
} else if (
typeof options.fd === "object" &&
ObjectPrototypeIsPrototypeOf(FileHandle.prototype, options.fd)
) {
// When fd is a FileHandle we can listen for 'close' events
if (options.fs) {
// FileHandle is not supported with custom fs operations
throw new ERR_METHOD_NOT_IMPLEMENTED("FileHandle with fs");
}
stream[kHandle] = options.fd;
stream[kFs] = FileHandleOperations(stream[kHandle]);
stream[kHandle][kRef]();
options.fd.on("close", FunctionPrototypeBind(stream.close, stream));
return options.fd.fd;
}
throw new ERR_INVALID_ARG_TYPE(
"options.fd",
["number", "FileHandle"],
options.fd,
);
}
export function ReadStream(path, options) {
if (!(ObjectPrototypeIsPrototypeOf(ReadStream.prototype, this))) {
return new ReadStream(path, options);
}
// A little bit bigger buffer and water marks by default
options = copyObject(getOptions(options, kEmptyObject));
if (options.highWaterMark === undefined) {
options.highWaterMark = 64 * 1024;
}
if (options.autoDestroy === undefined) {View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Await the open call first: const handle = await fsPromises.open(p, 'r'), then pass the handle.
- Pass the integer descriptor: handle.fd, or the number returned by fs.openSync.
- Deduplicate node:fs/promises imports (single copy in node_modules / one bundle) so prototype checks match.
Example fix
// before
const s = fs.createReadStream(p, { fd: fsPromises.open(p, 'r') }); // Promise, throws
// after
const handle = await fsPromises.open(p, 'r');
const s = fs.createReadStream(p, { fd: handle });
// or numeric fd
const s = fs.createReadStream(p, { fd: handle.fd }); Defensive patterns
Strategy: type-guard
Validate before calling
const fd = opts?.fd;
const ok = (typeof fd === 'number' && Number.isInteger(fd) && fd >= 0) ||
fd instanceof (await import('node:fs/promises')).FileHandle;
if (!ok) throw new TypeError('fd must be an awaited FileHandle or an integer descriptor'); Type guard
import { FileHandle } from 'node:fs/promises';
function isFd(v) {
return (typeof v === 'number' && Number.isInteger(v) && v >= 0) ||
v instanceof FileHandle;
} Try / catch
catch (e) {
if (e.code === 'ERR_INVALID_ARG_TYPE' && /options\.fd/.test(e.message)) {
throw new TypeError('fd must come from await fsPromises.open() or be an integer', { cause: e });
}
throw e;
} Prevention
- Always await fsPromises.open before using the result as fd.
- Type wrappers as fd: number | FileHandle and guard at the boundary.
- Avoid loading node:fs/promises from multiple bundle paths — prototype checks break.
When it happens
Trigger: fs.createReadStream(p, { fd: fsPromises.open(p, 'r') }) — passing the un-awaited Promise; { fd: '3' }; a FileHandle produced by a second loaded copy of node:fs/promises so ObjectPrototypeIsPrototypeOf(FileHandle.prototype, fd) is false.
Common situations: Mixing promise and callback/stream APIs and forgetting await; monorepos or bundlers that load two instances of the fs polyfill so prototype checks fail; passing a wrapped or duck-typed handle object from another library.
Understand the failure class
Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.
Related errors
- ERR_METHOD_NOT_IMPLEMENTED
- ERR_OUT_OF_RANGE
- ERR_INVALID_ARG_TYPE
- Unexpected 'name' field in options, bench name is already pr
- BenchContext::end() has already been invoked
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/09b188f482aa1946.
Report an issue: GitHub.