Skip to content

fs.openSync doesn't seem to have access to the vfs in sea mode. #302

Open

Description

@spotandjake

What version of pkg are you using?

6.22.0

What version of Node.js are you using?

24.21.0

What operating system are you using?

macOS

What CPU architecture are you using?

arm64

What Node versions, OSs and CPU architectures are you building for?

node24-macos-arm64

Describe the Bug

When I make the fs calls below, the readFileSync call resolves correctly however the openSync call fails with: no such file or directory, open '/snapshot/grain/stdlib/array.gr'

console.log(fs.readFileSync("/snapshot/grain/stdlib/array.gr", "utf8"));
console.log(fs.openSync("/snapshot/grain/stdlib/array.gr"));

If i'm not mistaken this is because the vfs-setup doesn't actually implement openSync and when the underlying MemoryProvider is initialized only the directories are loaded in and not the files. I looked into this a bit deeper I'm wondering if the actual problem I am running into is that installFsPatches in the vfs modules module_hooks doesn't seem to patch openSync at all.

Expected Behavior

The expected behaviour would t have it return a virtual file descriptor.

To Reproduce

I'm pretty sure you can just compare the result of openSync vs readFileSync on any file in the vfs.

Activity

  1. spotandjake commented on Sep 15, 2026

    @spotandjake
    Author

    I did a bit more research on this and there seems to be a few issues:

    PKG

    On the pkg side of things SeaProvider doesn't implement openSync which means operations that use it fallback to the underlying MemoryProvider implementation.

    On it's own this doesn't seem like a big problem as falling back to the memory provider is the correct behaviour however during initialization only the directories are written here and not files themselves.

    VFS

    Diving a bit deeper then that there seem to be two problems on the vfs side, one the vfs never patches fs.openSync in installFsPatches which means that the vfs is completely ignored.

    Beyond this issue

    Beyond this issue if openSync was implemented and patched fully it would give us a file descriptor, however given the other fs functions that consume file descriptor are not handled it wouldn't be very useful as it would be invalid in any context it would be used in. Furthur there doesn't seem to be logic implemented in the vfs module yet to determine which vfs a file descriptor, making this issue seem rather non trivial to fix.

  2. robertsLando commented on Sep 18, 2026

    @robertsLando
    Member

    Your diagnosis is right on both counts — SEAProvider has no openSync, and installFsPatches never patches open* at all. But the last paragraph is where I'd push back, because it's what makes this look non-trivial when it isn't:

    Further there doesn't seem to be logic implemented in the vfs module yet to determine which vfs a file descriptor [belongs to]

    That layer already exists in @roberts_lando/vfs@0.3.3:

    • lib/fd.js — openVirtualFd / getVirtualFd / closeVirtualFd over a module-level map, with virtual fds allocated from 10000+ specifically so they can't collide with real ones. That's the routing.
    • lib/file_system.js already implements the whole family: openSync (:400), closeSync (:406), readSync (:415), fstatSync (:423), plus async open (:513), close (:531), read (:545), fstat (:556). createReadStream already goes through it (lib/streams.js:43 calls vfs.openSync).

    So VirtualFileSystem is complete. Only two pieces are missing:

    1. pkg — implement SEAProvider.openSync, returning a handle backed by the existing readFileSync / _fileCache. MemoryProvider already defines MemoryFileHandle, so this is mostly wiring. The directories-only population you spotted is fine once the provider stops falling through to super.
    2. vfs — add open / openSync / read / readSync / close / closeSync / fstat / fstatSync (and fs.promises.open → FileHandle) to installFsPatches: route the path-taking one by mount point, the fd-taking ones via getVirtualFd(fd), and fall through to the real fs otherwise.

    That's the fix I'd rather have than #304. Two reasons:

    • It works for unmodified third-party libraries. Nothing in node_modules is going to reach for process.pkg.vfs, but plenty of it calls fs.openSync.
    • It restores classic-mode parity — prelude/bootstrap.js has patched the full descriptor family for years, so this is a SEA regression rather than a missing feature.

    I'll take both sides of this; I maintain the vfs fork too. Tracking the vfs half separately and will link it here.

    On #304: I left a longer note on the PR. Short version — exposing the instance doesn't actually give you a working openSync, because virtualFs.openSync lands on MemoryProvider.openSync, which looks up the in-memory tree where only directories were ever created. It'll throw the same ENOENT you're seeing now.

    Also seen: #305 (the realpathSync Windows path) and #301. Your toPlatformPath sketch in #305 looks right; I'll handle that one on the pkg side.

  3. robertsLando commented on Sep 18, 2026

    @robertsLando
    Member

    The vfs half is up:

    installFsPatches now patches openSync/open, readSync/read, closeSync/close and fstatSync/fstat, routing the path-taking ones by mount point and the fd-taking ones through the existing getVirtualFd table. Your repro passes:

    readFileSync: hello
    openSync fd: 10000
    readSync: 5 "hello"
    fstatSync size: 5
    

    Real-fs descriptors keep working alongside it — virtual fds start at 10000 precisely so they cannot shadow a real one.

    fs.promises.open is deliberately left out for now: it has to return a real FileHandle, and VirtualFileHandle has no .fd/createReadStream/readLines/chmod. It still falls through to the real fs, so no behaviour change there. Shout if you need it.

    Still to do on the pkg side: SEAProvider.openSync, so snapshot files resolve instead of falling through to MemoryProvider's directories-only tree. That is the other half of this issue and I will do it once the vfs release lands. #305 is on my list separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions