Repository navigation
Description
What version of Bun is running?
1.3.10+30e609e08
What platform is your computer?
Windows Server 2022 x64
(on GitHub Actions Windows x64 runners; also investigated locally on Windows x64)
What steps can reproduce the bug?
Use a workspace/monorepo that installs with the isolated linker on Windows and includes a patched dependency that resolves to multiple isolated peer/store variants.
Real failing case:
- workspace repo:
opencode - root has:
{
"packageManager": "[email protected]",
"patchedDependencies": {
"@standard-community/[email protected]": "patches/@standard-community%[email protected]"
}
}- transitive dep path includes
hono-openapi -> @standard-community/standard-openapi @standard-community/[email protected]is instantiated into multiple isolated store variants due to peer resolution
On Windows CI, run:
bun installIntermittently this fails with:
EPERM: Operation not permitted: failed to link package: @standard-community/[email protected] (link)Important repo shape details:
- workspace repo / isolated install
- patched dependency
- same patched package appears in multiple isolated store variants
- package has a nested file tree (
dist/vendors/...)
What is the expected behavior?
bun install should not mutate or replace a patched cache directory while isolated install tasks are hardlinking from it. A patched dependency should be materialized once per shared patched cache dir, then linked safely into all store variants.
What do you see instead?
Intermittent Windows failure during isolated install hardlinking:
EPERM: Operation not permitted: failed to link package: @standard-community/[email protected] (link)Additional information
The code path looks wrong in a deterministic way.
Relevant code refs:
- extract/download dedupe is keyed only by package name + version:
src/install/PackageManagerTask.zig:24
pub fn forNPMPackage(package_name: string, package_version: Semver.Version) Id {
hasher.update("npm-package:");
hasher.update(package_name);
hasher.update("@");
hasher.update(std.mem.asBytes(&package_version));
}- patched tarball tasks already apply the patch on successful extract:
src/install/PackageManagerTask.zig:77
if (this.status == .success) {
if (this.apply_patch_task) |pt| {
bun.handleOom(pt.apply());
}
}- after extract, preinstall state is set to
.done:src/install/PackageManager/runTasks.zig:699
manager.setPreinstallState(package_id, manager.lockfile, .done);- isolated install still re-applies the patch per store entry when
missing_from_cache == false:src/install/isolated_install.zig:970src/install/isolated_install.zig:995
const missing_from_cache = switch (manager.getPreinstallState(pkg_id)) {
.done => false,
else => missing_from_cache: {
if (patch_info == .none) { ... }
break :missing_from_cache true;
},
};
if (!missing_from_cache) {
if (patch_info == .patch) {
installer.applyPackagePatch(entry_id, patch_info.patch, &patch_log);
}
installer.startTask(entry_id);
}-
patched cache dir naming does not include peer hash:
src/install/isolated_install/Installer.zig:468src/install/PackageManager/PackageManagerDirectories.zig:342
-
isolated store dir naming does include peer hash:
src/install/isolated_install/Store.zig:131src/install/isolated_install/Store.zig:174
-
hardlinking walks a directory handle, but calls Windows hardlinking using an absolute source path string:
src/install/isolated_install/Hardlinker.zig:83
switch (sys.link(u16, this.src.sliceZ(), destfile_path)) {- Windows hardlink implementation:
src/sys.zig:3669
if (std.os.windows.kernel32.CreateHardLinkW(dest.ptr, src.ptr, null) == 0) {- Win32
ACCESS_DENIEDmaps toEPERM:src/errno/windows_errno.zig:1078
Win32Error.ACCESS_DENIED => SystemErrno.EPERM,Why this looks like a real race:
- one extracted patched package is shared by multiple isolated entries/store variants
- isolated install calls
applyPackagePatch()again per entry startTask()schedules hardlinking immediately after that:src/install/isolated_install/Installer.zig:29
- another entry can then re-patch / replace the same shared patched cache dir while the first hardlink task is already running
Observed package-specific anomaly in the failing repo:
@standard-community/standard-openapiis the only patched package in the graph- it is instantiated into 3 store variants
- it has the longest cache root among mapped packages in this install
- it is the only package in the measured set with all of:
- patched cache dir
- very long store root
- nested file depth
>= 2
I do not yet have a tiny public repro that deterministically fails inside Bun itself, but the source analysis above appears internally inconsistent enough to file:
- patch applied once during extract
- then patch applied again per isolated entry
- shared patched cache path across peer variants
- Windows hardlink from that path can surface as
EPERMwhen the source tree is mutated/replaced concurrently
Related:
Activity
fix(install): don't re-apply patch per peer variant in isolated install
feat(nix): add heal-peer-dep-bins pass + normalize-install orchestrator
handy: reproducible bun frontendDeps via normalize-install.ts
fix(app): spawn bun by absolute path in e2e-local to avoid Windows tr…
install: don't re-apply a patch per peer variant in isolated installs (…
ci(windows): pass --skip-install so build.ts does not undo the hoiste…
test(windows): skip flaky Bun 1.4.2 tests and fix Windows path handling
[Karina] fix(github): Windows 弃用 hoisted linker 并钉 bun 1.4.2
chore(ci): install with the isolated linker on Windows
fix(ci): run mcp-hub on a Bun that has the Windows patch fix
feat(mcp-hub): local Telegram server, catalog checks, cross-platform …