fix: work around Bun/Windows UV_FS_O_FILEMAP incompatibility in tar#16853
Conversation
Bun on Windows does not support the UV_FS_O_FILEMAP flag that tar@7 uses for files < 512KB (fs.open returns EINVAL). tar silently swallows the error and skips writing files, leaving only empty directories in node_modules. Set __FAKE_PLATFORM__=linux before arborist loads so tar falls back to the plain 'w' flag. Use dynamic import() for @npmcli/arborist to ensure the env var is set before tar evaluates its cached flag at module load time. Also removes stray console.log from Npm.install().
There was a problem hiding this comment.
Pull request overview
Works around a Windows-only Bun runtime incompatibility where tar@7 uses UV_FS_O_FILEMAP (unsupported by Bun) causing @npmcli/arborist installs to “succeed” while silently skipping file extraction.
Changes:
- Sets
process.env.__FAKE_PLATFORM__ = "linux"on Windows to forcetarto avoidUV_FS_O_FILEMAP. - Replaces static
@npmcli/arboristimport with dynamicawait import()so the env var is set before module evaluation. - Removes a stray
console.log(result)fromNpm.install().
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const { Arborist } = await import("@npmcli/arborist") | ||
| const arborist = new Arborist({ |
There was a problem hiding this comment.
The dynamic await import("@npmcli/arborist") here duplicates the same logic in install(). To keep the Bun/Windows workaround and import behavior consistent, consider extracting a shared helper (optionally caching the import promise) that returns Arborist and is reused by both call sites.
| const { Arborist } = await import("@npmcli/arborist") | ||
| const arb = new Arborist({ | ||
| path: dir, |
There was a problem hiding this comment.
Same as above: this second dynamic import is a separate spot that would need to stay in sync with the Bun/Windows workaround. Reusing a single getArborist() helper (and possibly caching the import) would reduce the chance of future drift.
| // Workaround: Bun on Windows does not support the UV_FS_O_FILEMAP flag that | ||
| // the `tar` package uses for files < 512KB (fs.open returns EINVAL). | ||
| // tar silently swallows the error and skips writing files, leaving only empty | ||
| // directories. Setting __FAKE_PLATFORM__ makes tar fall back to the plain 'w' | ||
| // flag. See tar's get-write-flag.js. | ||
| // Must be set before @npmcli/arborist is imported since tar caches the flag | ||
| // at module evaluation time — so we use a dynamic import() below. | ||
| if (process.platform === "win32") { | ||
| process.env.__FAKE_PLATFORM__ = "linux" | ||
| } |
There was a problem hiding this comment.
This sets process.env.__FAKE_PLATFORM__ as a module side-effect for every Windows run, but the workaround is described as Bun-on-Windows specific and it also globally “spoofs” the platform for any other dependency that might consult __FAKE_PLATFORM__. Consider scoping it to Bun (e.g. process.versions.bun) and setting/restoring the env var only immediately around the dynamic @npmcli/arborist import so the override doesn’t leak to the rest of the process.
8b5033b
into
anomalyco:refactor/lsp-core-improvements
Summary
Fixes the Windows-only test failure in
tool.registry > loads tools with external dependencies without crashing.Root Cause
tar@7usesUV_FS_O_FILEMAP(a libuv/Windows memory-mapped I/O flag) forfs.open()on all files < 512KB on Windows (get-write-flag.js). Bun does not support this flag —fs.open()returnsEINVAL. tar's error handler ([ONERROR]inunpack.js) treats this as a non-fatal warning and silently skips the file. The result:Arborist.reify()reports success, butnode_modulescontains only empty directory structures — no actual files.Evidence (step-by-step isolation)
fs.openSync(path, UV_FS_O_FILEMAP | O_TRUNC | O_CREAT | O_WRONLY)tar.x()withonwarn__FAKE_PLATFORM__=linuxbeforerequire('tar')thentar.x()require('arborist')thenarb.reify()import { Arborist }(static)await import("@npmcli/arborist")(dynamic)Fix
process.env.__FAKE_PLATFORM__ = "linux"at module top level on Windows — makes tar use plain'w'flag instead ofUV_FS_O_FILEMAPimport { Arborist }to dynamicawait import()— ensures the env var is set before tar evaluates its cached flag at module load timeconsole.log(result)fromNpm.install()