Skip to content

Windows isolated install can re-apply patched dependency per peer variant and fail with EPERM during hardlinking #28147

Closed

Description

@Hona

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 install

Intermittently 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:970
    • src/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:468
    • src/install/PackageManager/PackageManagerDirectories.zig:342
  • isolated store dir naming does include peer hash:

    • src/install/isolated_install/Store.zig:131
    • src/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_DENIED maps to EPERM:
    • 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-openapi is 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 EPERM when the source tree is mutated/replaced concurrently

Related:

Activity

  1. added a commit that references this issue on Mar 16, 2026
    c9c930f
  2. added a commit that references this issue on Jul 8, 2026
    bca6cd9
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