Skip to content

VirtualReadStream does not work with pipeline from node:stream/promises #64377

Description

@ajvincent

Version

v26.5.0

Platform

Darwin MacBook-Pro-2.local 25.5.0 Darwin Kernel Version 25.5.0: Tue Jun  9 22:28:34 PDT 2026; root:xnu-12377.121.10~1/RELEASE_ARM64_T6041 arm64

Subsystem

node:vfs

What steps will reproduce the bug?

node --experimental-vfs pipeline-readstream.mjs

pipeline-readstream.mjs contents:

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import {
  pipeline
} from "node:stream/promises";
import vfs from "node:vfs";

import {
  Writable,
} from "node:stream";

class TestWritable extends Writable {
  #chunks;
  constructor(chunks, options) {
    super(options);
    this.#chunks = chunks;
  }
  _write(data, encoding, callback) {
    void encoding;
    this.#chunks.push(data.toString());
    callback(null);
  }
}

async function * typeofChunk(source) {
  for await (const chunk of source) {
    yield typeof chunk;
  }
}

async function doPipelineTest(inStream) {
  const chunks = [];
  const outStream = new TestWritable(chunks);

  await pipeline(
    inStream,
    typeofChunk,
    outStream
  );
  return chunks;
}

async function buildRealFS_ReadStream() {
  const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "ts-morph-structures-"));

  const pathToHello = path.join(tempDir, "helloWorld.txt");
  await fs.promises.writeFile(pathToHello, "hello world\n", { encoding: "utf-8" });

  const d = Promise.withResolvers();
  d.promise = d.promise.then(() => fs.promises.rm(tempDir, { recursive: true }));

  const inStream = fs.createReadStream(pathToHello, { encoding: "utf-8" });
  return [inStream, { tempDir, ...d }];
}

async function buildVFS_ReadStream() {
  const provider = new vfs.MemoryProvider();
  const fileSystem = vfs.create(
    provider,
    { emitExperimentalWarning: false }
  );
  await fileSystem.promises.writeFile("/helloWorld.txt", "hello world\n", { encoding: "utf-8" });

  // code using node:fs's createReadStream instead passes this test
  return fileSystem.createReadStream(
    "/helloWorld.txt",
    { encoding: "utf-8" }
  );
}

async function runRealFSTest() {
  const [inStream, tempDirWithCleanup] = await buildRealFS_ReadStream();
  try {
    const chunks = await doPipelineTest(inStream);
    console.log("chunks.length for real FS: " + chunks.length);
    // chunks.length for real FS: 1
  }
  finally {
    tempDirWithCleanup.resolve();
    await tempDirWithCleanup.promise;
  }
}

async function runVirtualFSTest() {
  const inStream = await buildVFS_ReadStream();
  const chunks = await doPipelineTest(inStream);
  /*
Error: EBADF: bad file descriptor, read
    at VirtualReadStream._read (node:internal/vfs/streams:125:20)
    at Readable.read (node:internal/streams/readable:753:12)
    at createAsyncIterator (node:internal/streams/readable:1406:54)
    at createAsyncIterator.next (<anonymous>)
    at typeofChunk (.../pipeline-readstream.mjs:27:20)
    at typeofChunk.next (<anonymous>)
    at pumpToNode (node:internal/streams/pipeline:133:22)
    at pipelineImpl (node:internal/streams/pipeline:384:9)
    at node:stream/promises:31:5
    at new Promise (<anonymous>) {
  errno: -9,
  syscall: 'read',
  code: 'EBADF'
}

Node.js v26.5.0
  */

  console.log("chunks.length for vfs: " + chunks.length);
};

await runRealFSTest();
await runVirtualFSTest();

How often does it reproduce? Is there a required condition?

100% reproducible

What is the expected behavior? Why is that the expected behavior?

chunks.length for real FS: 1
chunks.length for vfs: 1

What do you see instead?

chunks.length for real FS: 1
node:internal/modules/run_main:107
    triggerUncaughtException(
    ^

Error: EBADF: bad file descriptor, read
    at VirtualReadStream._read (node:internal/vfs/streams:125:20)
    at Readable.read (node:internal/streams/readable:753:12)
    at createAsyncIterator (node:internal/streams/readable:1406:54)
    at createAsyncIterator.next (<anonymous>)
    at typeofChunk (.../pipeline-readstream.mjs:27:20)
    at typeofChunk.next (<anonymous>)
    at pumpToNode (node:internal/streams/pipeline:133:22)
    at pipelineImpl (node:internal/streams/pipeline:384:9)
    at node:stream/promises:31:5
    at new Promise (<anonymous>) {
  errno: -9,
  syscall: 'read',
  code: 'EBADF'
}

Node.js v26.5.0

Additional information

I am well aware node:vfs is experimental. I was trying this as an experiment.

Metadata

Metadata

Assignees

No one assigned

    Labels

    vfsIssues and PRs related to the virtual filesystem subsystem.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions