Title: Virtual File System Follow Ups · Issue #62328 · nodejs/node · GitHub
Open Graph Title: Virtual File System Follow Ups · Issue #62328 · nodejs/node
X Title: Virtual File System Follow Ups · Issue #62328 · nodejs/node
Description: /cc @mcollina With The Virtual File System PR getting close to landing, there are a number of areas / issues to follow-up on. Flagging here in a new issue to prevent overloading the PR discussion. Code Review: PR #61478 — Virtual File Sy...
Open Graph Description: /cc @mcollina With The Virtual File System PR getting close to landing, there are a number of areas / issues to follow-up on. Flagging here in a new issue to prevent overloading the PR discussion. ...
X Description: /cc @mcollina With The Virtual File System PR getting close to landing, there are a number of areas / issues to follow-up on. Flagging here in a new issue to prevent overloading the PR discussion. ...
Opengraph URL: https://github.com/nodejs/node/issues/62328
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Virtual File System Follow Ups","articleBody":"/cc @mcollina \n\nWith The [Virtual File System](https://github.com/nodejs/node/pull/61478) PR getting close to landing, there are a number of areas / issues to follow-up on. Flagging here in a new issue to prevent overloading the PR discussion.\n\n# Code Review: PR #61478 — Virtual File System for Node.js\n\n1. [Security \u0026 Permission Model](#1-security--permission-model)\n2. [API Compatibility Gaps](#2-api-compatibility-gaps)\n3. [Correctness Bugs](#3-correctness-bugs)\n4. [Windows Path Handling](#4-windows-path-handling)\n5. [Performance Concerns](#5-performance-concerns)\n6. [Architecture \u0026 Design](#6-architecture--design)\n7. [Test Runner Mock Integration](#7-test-runner-mock-integration)\n8. [Provider-Specific Issues](#8-provider-specific-issues)\n9. [Test Coverage Gaps](#9-test-coverage-gaps)\n10. [Code Quality \u0026 Cleanup](#10-code-quality--cleanup)\n\n---\n\n## 1. Security \u0026 Permission Model\n\n**We need to decide if VFS should participate in the permissions model and how.**\n\n### 1.1 VFS interception bypasses the Node.js permission model — CRITICAL\n\n**Disposition: follow-up**\n\nIn `lib/fs.js`, VFS interception happens *before* the permission model\ncheck in every intercepted function. Pattern (repeated 96+ times):\n\n```js\nfunction readFileSync(path, options) {\n const h = vfsState.handlers;\n if (h !== null) {\n const result = h.readFileSync(path, options);\n if (result !== undefined) return result; // ← returns BEFORE permission check\n }\n // permission check is down here\n if (permission.isEnabled() \u0026\u0026 !permission.has('fs.read', path)) { ... }\n}\n```\n\nWhen `--experimental-permission` is active, a VFS mount can serve\ncontent for any path without triggering the permission gate. This is\nthe most significant security issue in the PR.\n\n**Recommendation:** At minimum, when `permission.isEnabled()`, the\nVFS handler path should still check `permission.has('fs.read', ...)` /\n`permission.has('fs.write', ...)` as appropriate. Alternatively,\ndisable VFS mounting entirely when the permission model is active\nuntil a proper integration is designed.\n\n### 1.2 Any loaded code can call `mount()` and shadow real paths — HIGH\n\n**Disposition: follow-up**\n\n`require('node:vfs')` is a public module with no gating. Any npm\ndependency can create a VFS, write arbitrary content, and `mount('/')`\nto intercept every `fs` call process-wide. There are no restrictions\non mount paths — `/etc`, `/usr`, `node_modules`, or `/` itself are\nall valid targets.\n\nCombined with overlay mode, this enables targeted, nearly undetectable\ninterception of specific files (e.g., `.env`, credentials, SSH keys)\nwhile passing all other reads through to the real filesystem.\n\nThis was raised by @Qard in\nhttps://github.com/nodejs/node/pull/61478#issuecomment-2868746178\nand the `vfs-mount` event was removed without a replacement gate.\n\n**Recommendation:** Consider:\n- Requiring `--experimental-vfs` flag to enable the module\n- Emitting an observable event on mount/unmount for security tooling\n- Restricting mount paths (blocklist for system-critical paths, or allowlist)\n\n### 1.3 Overlapping mounts have undefined behavior — MEDIUM\n\n**Disposition: follow-up**\n\n`activeVFSList` in `setup.js` is iterated linearly. First-registered\nVFS wins. If VFS-A mounts at `/app` and VFS-B at `/app/src`, reads\nto `/app/src/file.js` hit VFS-A. In mounted (non-overlay) mode,\nVFS-A returns ENOENT without consulting VFS-B. No conflict detection\nor warning exists.\n\n**Recommendation:** `registerVFS()` should detect and warn (or reject)\noverlapping mount points.\n\n### 1.4 `RealFSProvider` symlink target not validated — MEDIUM\n\n**Disposition: follow-up**\n\n`providers/real.js:315-318` — `symlinkSync(target, vfsPath, type)`\nvalidates `vfsPath` via `#resolvePath()` but passes `target` through\nto `fs.symlinkSync` without validation. A user can create a symlink\ninside the VFS root pointing to any path on the real filesystem,\nenabling a jail escape.\n\nSimilarly, `readlinkSync` may return absolute real-filesystem paths\nwithout translating them into VFS coordinates, leaking path info.\n\nAnd `realpathSync` silently returns the original VFS path when the\nresolved path escapes the root (lines 336-337, 350-351), masking\nthe escape rather than throwing.\n\n---\n\n## 2. API Compatibility Gaps\n\nThese are places where VFS objects don't match the behavior of their\nreal `fs` counterparts. Code that works with real `fs` will break in\nsubtle ways when given VFS equivalents.\n\n### 2.1 `VirtualFileHandle` missing ~15 methods — HIGH\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/file_handle.js` — `VirtualFileHandle` is missing\nthese methods that exist on real `fs.promises.FileHandle`:\n\n- `chmod(mode)`, `chown(uid, gid)`, `utimes(atime, mtime)`\n- `datasync()`, `sync()`\n- `readv(buffers)`, `writev(buffers)`\n- `appendFile(data)`, `readLines()`, `readableWebStream()`\n- `createReadStream()`, `createWriteStream()`\n- `fd` property (numeric file descriptor)\n- `[Symbol.asyncDispose]()`\n\nAdditionally, `VirtualFileHandle` does not extend `EventEmitter`, so\nthe `'close'` event (available since v15.4.0) doesn't fire. Code\ncalling `filehandle.on('close', ...)` will throw.\n\n**Recommendation:** Add stub methods that throw\n`ERR_METHOD_NOT_IMPLEMENTED` for unimplemented APIs, and extend\n`EventEmitter`.\n\n### 2.2 `fsPromises.open()` not intercepted by VFS — HIGH\n\n**Disposition: follow-up**\n\n`lib/internal/fs/promises.js` — The `open()` function goes directly\nto `binding.openFileHandle()` without any VFS check. Any code using\nthe modern `FileHandle` API via `fsPromises.open()` bypasses VFS\nentirely. This includes all `FileHandle` methods (`fh.read()`,\n`fh.write()`, `fh.stat()`, `fh.close()`).\n\n### 2.3 Streams missing standard properties — HIGH\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/streams.js`:\n\n- `VirtualReadStream` — missing `bytesRead` and `pending` properties\n- `VirtualWriteStream` — missing `bytesWritten`, `pending` properties,\n and explicit `close(callback)` method\n\n### 2.4 `VirtualDir` missing disposal and auto-close — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/dir.js`:\n\n- Missing `[Symbol.asyncDispose]()` and `[Symbol.dispose]()`\n- `entries()` async iterator doesn't auto-close the directory after\n iteration (real `fs.Dir` does)\n- `read()` and `close()` are `async` methods that always return a\n Promise, even in callback mode (real `fs.Dir` returns `undefined`\n when a callback is provided)\n\n### 2.5 Stats objects have `ino: 0` and `dev: 0` for all files — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/stats.js` — Every virtual file gets `ino = 0` and\n`dev = 0`. This means:\n\n- Code that uses `stat.ino` for identity checks (e.g., detecting hard\n links, circular references in `fs.cp`) treats all VFS files as the\n same file\n- `stat.dev === 0` could collide with real device IDs\n\n**Recommendation:** Generate unique inode numbers (incrementing\ncounter) and use a distinctive `dev` number for VFS files.\n\n### 2.6 No `bigint` Stats support — LOW\n\n**Disposition: note**\n\nThe stats factory functions don't handle `{ bigint: true }`. Callers\npassing this option get regular Stats without bigint values. Unlikely\nto matter for most use cases but worth documenting.\n\n### 2.7 `VFSWatcher` / `VFSStatWatcher` API mismatches — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/watcher.js`:\n\n- `VFSWatcher` never emits `'error'` event — errors during polling\n are silently swallowed in `#getStats()` and `#getStatsFor()`\n- `VFSStatWatcher.addListener(listener)` and\n `removeListener(listener)` break the `EventEmitter` contract — they\n take only one argument instead of `(event, listener)`, shadowing\n the inherited methods with incompatible signatures\n\n---\n\n## 3. Correctness Bugs\n\n### 3.1 Streams stall on destroyed/null fd — HIGH\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/streams.js`:\n\n- `VirtualWriteStream._write()` (lines 255-258): When\n `this.#destroyed || this.#fd === null`, the method returns *without*\n calling `callback()`. The writable stream will hang indefinitely.\n **Must** call `callback(error)` or `callback()`.\n\n- `VirtualReadStream._read()` (lines 94-97): Same pattern — returns\n without pushing `null` or calling `destroy()`. The readable stream\n stalls.\n\n### 3.2 `#checkClosed()` always reports `'read'` syscall — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/file_handle.js` — Both `VirtualFileHandle` and\n`MemoryFileHandle` implementations of `#checkClosed()` always throw\n`createEBADF('read')`, even when called from write, truncate, or stat\noperations. The error message will be misleading.\n\n### 3.3 `writeFileSync` misses `'ax'` / `'ax+'` append flags — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/file_handle.js:508` — The append-mode check only\nhandles `'a'` and `'a+'`, missing `'ax'` and `'ax+'`. Data written\nvia `writeFileSync` with `flags: 'ax'` will replace content instead\nof appending. The `#isAppend()` method already handles all four\nvariants and should be used instead.\n\n### 3.4 Dead ternary in `#hookProcessCwd` — LOW\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/file_system.js:282-283`:\n\n```js\nconst normalized = isAbsolute(directory) ?\n resolvePath(directory) :\n resolvePath(directory);\n```\n\nBoth branches are identical. Likely a leftover from a refactor.\n\n### 3.5 `SEAProvider.statSync` reads full asset to get size — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/providers/sea.js:304` — `statSync` calls\n`this.#getAssetContent(normalized)` which copies the entire asset into\na new Buffer just to measure `.length`. For large embedded assets,\nthis is wasteful. A size-only path or caching would help.\n\n### 3.6 `SEAProvider` doesn't handle recursive `readdir` — MEDIUM\n\n**Disposition: follow-up**\n\n`providers/sea.js` — `readdirSync` does not handle `{ recursive: true }`.\nCalled with that option, it silently returns only immediate children.\n\n### 3.7 `SEAProvider.openSync` only accepts string flag `'r'` — LOW\n\n**Disposition: follow-up**\n\n`providers/sea.js:273` — Rejects numeric flags like `O_RDONLY = 0`.\nThe `MemoryProvider` handles this via `normalizeFlags()` but\n`SEAProvider` does not.\n\n### 3.8 Shared mutable `statsArray` is fragile — MEDIUM\n\n**Disposition: note**\n\n`lib/internal/vfs/stats.js:27` — A module-level `Float64Array(18)`\nsingleton is reused across all stats creation calls. This is safe only\nbecause `getStatsFromBinding()` copies synchronously. Any future\nrefactor that makes it async will introduce silent data corruption.\nShould at minimum have a comment asserting this invariant.\n\n---\n\n## 4. Windows Path Handling\n\n### 4.1 Case-insensitive path comparison missing — CRITICAL\n\n**Disposition: follow-up (depending on Windows CI status)**\n\nAll path comparisons in the VFS are case-sensitive:\n\n- `router.js:30` — `normalizedPath === mountPoint` (strict equality)\n- `router.js:40` — `StringPrototypeStartsWith(normalizedPath, prefix)`\n- `memory.js:293` — `current.children.get(segment)` (Map is case-sensitive)\n\nOn Windows, `C:\\Virtual` and `c:\\virtual` and `C:\\VIRTUAL` are the\nsame path, but the VFS treats them as different. A case-variant access\nsilently bypasses the VFS and falls through to the real filesystem.\n\nThis is both a correctness bug and a security vulnerability if the VFS\nis used for sandboxing.\n\n**Recommendation:** On `process.platform === 'win32'`, normalize paths\nto a canonical case before comparison. Provider Map lookups should\nalso be case-insensitive on Windows.\n\n### 4.2 UNC paths (`\\\\server\\share`) unsupported — HIGH\n\n**Disposition: follow-up**\n\nZero UNC path handling exists. The `MemoryProvider`'s\n`#normalizePath` converts `\\\\server\\share\\file` →\n`//server/share/file` → `/server/share/file` (POSIX normalize\ncollapses `//`), destroying the UNC prefix. Should either be\nexplicitly rejected with a clear error or properly supported.\n\n### 4.3 Virtual CWD uses hardcoded `/` separator — MEDIUM\n\n**Disposition: follow-up**\n\n`file_system.js:211` — `const resolved = \\`${this[kVirtualCwd]}/${inputPath}\\``\nuses a hardcoded `/` to join paths. On Windows this produces mixed\nseparators like `C:\\virtual\\subdir/relative/path`. While `resolvePath()`\nnormalizes this on the next line, it's fragile. Should use\n`resolvePath(this[kVirtualCwd], inputPath)` directly.\n\n### 4.4 `findVFSPackageJSON` mixed separator check — LOW\n\n**Disposition: note**\n\n`setup.js:352-353` checks both `'/node_modules'` and\n`'\\\\node_modules'`. On each platform one branch is dead code. Using\n`sep + 'node_modules'` would be cleaner.\n\n---\n\n## 5. Performance Concerns\n\n### 5.1 Async callback APIs are sync under the hood — HIGH\n\n**Disposition: follow-up**\n\nThroughout the codebase, callback-based async fs functions delegate to\nsync VFS operations wrapped in `process.nextTick`. This affects:\n\n- **`lib/fs.js`** — All 96+ callback interception points call the\n sync handler and deliver results via `nextTick`\n- **`file_system.js`** — `rm()`, `truncate()`, `ftruncate()`,\n `link()`, `mkdtemp()`, `opendir()` all call their sync counterparts\n- **`MemoryProvider`** — Every async method calls the sync counterpart\n- **`SEAProvider`** — Same pattern\n\nThis means the async API provides zero concurrency benefit and blocks\nthe event loop. For the in-memory and SEA providers this is pragmatic,\nbut for `RealFSProvider` (which has genuine async I/O) or user-created\ncustom providers, this is a real problem since the `lib/fs.js`\ninterception layer forces sync execution regardless of provider\ncapability.\n\n**Recommendation:** Document this limitation. Consider allowing the\nhandlers in `setup.js` to return a Promise that the `lib/fs.js`\ncallback path can handle asynchronously.\n\n### 5.2 `existsSync` called in async code paths — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/setup.js:236` — `findVFSWithAsync()` calls\n`vfs.existsSync()` (sync!) inside an async function. If a custom\nprovider's `existsSync` is expensive (e.g., network-backed), this\nblocks the event loop in a nominally async path.\n\n### 5.3 Linear scan of `activeVFSList` — LOW\n\n**Disposition: note**\n\nAll `findVFSFor*` functions iterate the active VFS list linearly.\nWith many mounted VFS instances this could become slow, though in\npractice the list is likely very small (1-3 instances).\n\n---\n\n## 6. Architecture \u0026 Design\n\n### 6.1 Maintenance burden of 164+ interception points — HIGH\n\n**Disposition: follow-up (design discussion)**\n\nThe VFS integration adds:\n- ~96 interception blocks in `lib/fs.js`\n- ~68 interception blocks in `lib/internal/fs/promises.js`\n\nEvery new `fs` API must remember to add a VFS hook. This is a\nsignificant ongoing maintenance burden and a source of future bugs\nwhen hooks are forgotten.\n\n**Recommendation:** Consider whether the interception could be\ncentralized (e.g., a Proxy-based approach, or a single dispatch\nfunction that handles all operations) to reduce the per-function\nboilerplate.\n\n### 6.2 Cross-VFS operations not handled — MEDIUM\n\n**Disposition: follow-up**\n\n`setup.js` — `renameSync`, `copyFileSync`, `linkSync` handlers\nresolve `newPath`/`dest` using the source VFS. If `newPath` is in a\ndifferent VFS mount or outside any VFS, the operation is still\ndispatched to the source VFS, which will likely fail in confusing\nways.\n\n### 6.3 Package.json cache not invalidated on unmount — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/modules/package_json_reader.js` caches in\n`moduleToParentPackageJSONCache` and `deserializedPackageJSONCache`\nare never cleared when a VFS is unmounted. Only the CJS `_pathCache`\nand stat cache are cleared (in `setup.js:92-93`). Stale VFS\npackage.json data persists after unmount.\n\n### 6.4 C++ format coupling in package.json serialization — MEDIUM\n\n**Disposition: note**\n\n`setup.js` `serializePackageJSON()` must produce tuples matching the\nexact format of the C++ `readPackageJSON` binding. The VFS does a\nparse→serialize→deserialize round-trip (JSON parse the file, extract\nfields, stringify certain fields, then the reader deserializes again).\nIf the C++ format changes, the JS VFS serialization must be updated\ntoo. This is a fragile contract that should be documented with a\ncross-reference.\n\n### 6.5 `legacyMainResolve` has hardcoded extension arrays — LOW\n\n**Disposition: note**\n\n`setup.js` — The VFS implementation of `legacyMainResolve` hardcodes\nextension arrays matching the C++ implementation. If the C++ side\nchanges the extension mapping, this will silently diverge.\n\n---\n\n## 7. Test Runner Mock Integration\n\n### 7.1 `MockFSContext.restore()` error prevents other mock cleanup — MEDIUM\n\n**Disposition: follow-up**\n\n`lib/internal/test_runner/mock/mock.js` — If `vfs.unmount()` throws\ninside `restore()`, the error propagates through `restoreAll()` which\nhas no try/catch, preventing subsequent mocks from being restored.\n\n### 7.2 `MockFSContext.vfs` property exposes full VFS instance — LOW\n\n**Disposition: note**\n\nThe public `.vfs` property gives test code direct access to the full\n`VirtualFileSystem` instance, allowing unintended operations like\ncalling `mount()` at a different prefix. Consider making this a\nprivate field.\n\n### 7.3 Platform-specific `parentDir !== '/'` check — LOW\n\n**Disposition: follow-up**\n\n`mock.js:441` — `if (parentDir !== '/')` doesn't account for Windows\nroots like `C:\\`. Should use `path.parse()` or compare against the\nmount prefix root.\n\n---\n\n## 8. Provider-Specific Issues\n\n### 8.1 `MemoryProvider`: `statSync` reports size 0 for dynamic files — MEDIUM\n\n**Disposition: follow-up**\n\n`providers/memory.js:408` — When a file has a `contentProvider` (dynamic\ncontent) and no explicit `size`, `statSync` reads `entry.content.length`\nwhich is the initial empty buffer (`Buffer.alloc(0)`), reporting size 0.\n\n### 8.2 `MemoryProvider`: `symlinkSync` auto-creates parent dirs — LOW\n\n**Disposition: note**\n\n`providers/memory.js:808` — `symlinkSync` passes `create=true` to\n`#ensureParent`, auto-creating intermediate directories. Other\nmutating operations pass `create=false`. This is inconsistent and\ncould surprise users.\n\n### 8.3 `MemoryProvider`: Recursive `readdir` doesn't follow symlinks — LOW\n\n**Disposition: note**\n\n`providers/memory.js:597` — `#readdirRecursive` checks\n`childEntry.isDirectory()` directly without following symlinks. This\nmay differ from real `readdir` behavior where `{ recursive: true }`\nfollows symlinks.\n\n### 8.4 `RealFSProvider`: Path traversal via symlinks — MEDIUM\n\n**Disposition: follow-up**\n\n`providers/real.js` — `#resolvePath` validates the logical path but\ndoes NOT resolve symlinks before checking. If the real filesystem has\na symlink inside `rootPath` pointing outside it, operations follow\nthat symlink and access files outside the jail. A proper chroot would\nneed `fs.realpathSync` in `#resolvePath` or a post-resolution check.\n\n### 8.5 `RealFSProvider`: No watch support — LOW\n\n**Disposition: note**\n\nThe real filesystem provider doesn't implement `watch`/`watchFile`/\n`unwatchFile` despite wrapping a real filesystem that could easily\nsupport them.\n\n---\n\n## 9. Test Coverage Gaps\n\n### 9.1 Windows tests are minimal — HIGH\n\n**Disposition: follow-up**\n\n`test/parallel/test-vfs-windows.js` has only 4 test cases (100 lines).\nMissing coverage:\n\n| Missing Test | Priority |\n|-------------|----------|\n| Case-insensitive path matching | CRITICAL |\n| UNC paths | HIGH |\n| Mixed separators (`C:\\virtual/subdir\\file.txt`) | HIGH |\n| Path traversal with `..` on Windows | HIGH |\n| Forward-slash Windows paths (`C:/virtual/file.txt`) | MEDIUM |\n| Overlay mode on Windows | MEDIUM |\n| Virtual CWD on Windows | MEDIUM |\n| Write operations through `fs` on Windows | MEDIUM |\n\n### 9.2 `fsPromises.open()` / FileHandle path untested — HIGH\n\n**Disposition: follow-up**\n\nNo tests verify that `fsPromises.open()` works with VFS paths\n(because it doesn't — see §2.2). Tests should be added once this\nis supported.\n\n### 9.3 Overlapping mount behavior untested — MEDIUM\n\n**Disposition: follow-up**\n\nNo tests verify behavior when multiple VFS instances mount at\noverlapping paths.\n\n### 9.4 Permission model interaction untested — MEDIUM\n\n**Disposition: follow-up**\n\nNo tests verify VFS behavior when `--experimental-permission` is\nactive.\n\n---\n\n## 10. Code Quality \u0026 Cleanup\n\n### 10.1 Watcher unbounded memory growth — HIGH\n\n**Disposition: follow-up**\n\n`lib/internal/vfs/watcher.js`:\n\n- `VFSWatchAsyncIterable.#pendingEvents` grows without bound if\n the consumer reads slower than events arrive\n- `#pendingResolvers` grows without bound if `next()` is called\n many times without events\n- No backpressure mechanism exists\n\n### 10.2 Redundant `#destroyed` field in streams — LOW\n\n**Disposition: note**\n\nBoth `VirtualReadStream` and `VirtualWriteStream` track `#destroyed`\nmanually, but `Readable` and `Writable` already have a `destroyed`\nproperty.\n\n### 10.3 `SEAProvider` primordials inconsistency — LOW\n\n**Disposition: follow-up**\n\n`providers/sea.js`:\n- Line 226: Uses `path.replace()` regex instead of\n `StringPrototypeReplaceAll` from primordials\n- Line 335: Uses spread `[...children]` instead of `ArrayFrom`\n from primordials\n\n### 10.4 `VirtualFileHandle` private method inaccessible to subclass — LOW\n\n**Disposition: note**\n\n`file_handle.js` — `#checkClosed()` is a private method on the base\nclass, but private methods aren't inherited. `MemoryFileHandle`\nre-declares its own `#checkClosed()` at line 261. The duplication\nconfirms the pattern doesn't work cleanly. Consider making it a\nregular method or using the symbol-based pattern already used for\nproperties.\n\n### 10.5 `file_system.js` and `setup.js` are very large — LOW\n\n**Disposition: note**\n\n`file_system.js` (1347 lines) and `setup.js` (1080 lines) are\nsubstantial, driven by the three API surfaces (sync/callback/promise)\nand comprehensive fs handler delegation. The code is functional but\ndifficult to navigate. Consider splitting in follow-ups.\n\n### 10.6 `VirtualFD` range starts at 10,000 — LOW\n\n**Disposition: note**\n\n`lib/internal/vfs/fd.js:18` — Virtual FDs start at 10,000 to avoid\ncollision with real OS FDs. As noted in a PR comment by @arcanis, this\nis low for long-running apps that open/close many files. The fslib\nproject reserves the upper byte of an i32 instead. No FD reuse or\nwraparound exists. Worth revisiting in a follow-up.\n\n---\n","author":{"url":"https://github.com/jasnell","@type":"Person","name":"jasnell"},"datePublished":"2026-03-18T21:10:52.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":9},"url":"https://github.com/62328/node/issues/62328"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:75259c7c-d9dd-5cb7-64a6-5a5f3268f30b |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 902E:13B1D:279BC2:36B0FF:6A4C6CC9 |
| html-safe-nonce | 9d9e739e61c4c41e13d8ac5315c0c8f976d2db751874f3b299f332742b996f29 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5MDJFOjEzQjFEOjI3OUJDMjozNkIwRkY6NkE0QzZDQzkiLCJ2aXNpdG9yX2lkIjoiMjY4NDA3MTkyNzE2NTUxMjkwNSIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 501add35bdb4eeeebe2d43dd0e28838125a453be3b3e8e1ba8f5e4f17f9b4964 |
| hovercard-subject-tag | issue:4097736900 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/62328/issue_layout |
| twitter:image | https://opengraph.githubassets.com/e68d47864ac76f3bf44df5bd4750219682ba5791ace54650e7e226fb92bb5aee/nodejs/node/issues/62328 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/e68d47864ac76f3bf44df5bd4750219682ba5791ace54650e7e226fb92bb5aee/nodejs/node/issues/62328 |
| og:image:alt | /cc @mcollina With The Virtual File System PR getting close to landing, there are a number of areas / issues to follow-up on. Flagging here in a new issue to prevent overloading the PR discussion. ... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | jasnell |
| hostname | github.com |
| expected-hostname | github.com |
| None | 3d11bb817438277de2a940854450e83a7d32b6aeb5014e9e6b00a6423900251c |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 14099438da5379150f15a2892474c7c7e6c0e55e |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width