#43109·bun

`fs.openAsBlob()` reads the file's new contents after it is modified instead of rejecting as Node does

Author: tomer-antCreated Sep 17, 2026Updated Sep 17, 2026

What version of Bun is running?

1.4.2+744846f84 (also reproduced on 1.3.14+0d9b296af)

What platform is your computer?

Darwin 25.6.0 arm64 arm

What steps can reproduce the bug?

Bun implements fs.openAsBlob() as Promise.resolve(Bun.file(path, options)), so every read of the returned Blob returns the path's current contents. Node documents the opposite contract:

The file must not be modified after the Blob is created. Any modifications will cause reading the Blob data to fail with a DOMException error. Synchronous stat operations on the file when the Blob is created, and before each read in order to detect whether the file data has been modified on disk.

Save as repro.mjs and run node repro.mjs, then bun repro.mjs:

import fs from "node:fs";
import os from "node:os";
import path from "node:path";

const file = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "openAsBlob-")), "a.txt");
fs.writeFileSync(file, "hello");

const blob = await fs.openAsBlob(file);
fs.writeFileSync(file, "swapped!"); // modify after the Blob is created

console.log("runtime:", typeof Bun === "undefined" ? `node ${process.version}` : `bun ${Bun.version}`);
console.log("blob.size:", blob.size);
try {
  console.log("text():", JSON.stringify(await blob.text()));
} catch (err) {
  console.log("text() rejected:", err.name, "-", err.message);
}

What is the expected behavior?

What Node v22.22.3 prints, per the docs quoted above:

runtime: node v22.22.3
blob.size: 5
text() rejected: NotReadableError - The blob could not be read

What do you see instead?

runtime: bun 1.4.2
blob.size: 8
text(): "swapped!"

Additional information

More cases from the same cause. In each, Node rejects the read with NotReadableError from text(), arrayBuffer(), stream(), and slice().text() alike.

  • After a same-length overwrite with "HELLO", Bun returns "HELLO". Node still rejects, so its check is not on size alone.
  • If blob.size is read before the write, it stays 5 afterwards. text() then returns "swapp", the new contents cut to the old size, and blob.slice(1, 4).text() returns "wap".
  • With size read first and the file then truncated to "hi", text() returns "hi" while size stays 5.

A missing path differs too: await fs.openAsBlob("/nonexistent") resolves with a size-0 Blob on Bun, and Node rejects with ERR_INVALID_ARG_VALUE ("Unable to open file as blob").

Code that calls openAsBlob() to get a replayable request body, for example to retry a multipart upload, depends on this check. If the file changed between attempts, the retry fails on Node, and on Bun it sends bytes that differ from the first attempt's.

Lazy reads are documented for Bun.file(), so this report is only about node:fs. #40339 (MIME type inferred from the extension) comes from the same delegation to Bun.file(). openAsBlob was added in #9628.