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

  1. Await the open call first: const handle = await fsPromises.open(p, 'r'), then pass the handle.
  2. Pass the integer descriptor: handle.fd, or the number returned by fs.openSync.
  3. 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

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


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