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.
Version
v26.5.0
Platform
Subsystem
node:vfs
What steps will reproduce the bug?
node --experimental-vfs pipeline-readstream.mjspipeline-readstream.mjs contents:
How often does it reproduce? Is there a required condition?
100% reproducible
What is the expected behavior? Why is that the expected behavior?
What do you see instead?
Additional information
I am well aware
node:vfsis experimental. I was trying this as an experiment.