-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
What version of Bun is running?
1.3.10+30e609e08
What platform is your computer?
Microsoft Windows NT 10.0.22631.0 x64
What steps can reproduce the bug?
Two fresh project dirs, same package.json, same fresh BUN_INSTALL_CACHE_DIR, and run bun install --no-cache --verbose in parallel.
Serial with the same shared cache passes. Parallel with the same shared cache fails.
Minimal repro:
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"
import { tmpdir } from "node:os"
import path from "node:path"
const bun = process.execPath
const root = await mkdtemp(path.join(tmpdir(), "bun-install-race-"))
const cache = path.join(root, "cache")
const pkg = JSON.stringify(
{
name: "bun-install-race",
private: true,
packageManager: "bun@1.3.10",
dependencies: {
"@opencode-ai/plugin": "1.2.25",
},
},
null,
2,
)
const write = async (file: string, content: string) => {
await mkdir(path.dirname(file), { recursive: true })
await writeFile(file, content)
}
const setup = async (dir: string) => {
await write(path.join(dir, "package.json"), pkg)
}
const reset = async (dir: string) => {
await rm(path.join(dir, "node_modules"), { recursive: true, force: true })
await rm(path.join(dir, "bun.lock"), { force: true })
await rm(path.join(dir, "package-lock.json"), { force: true })
}
const run = async (cwd: string) => {
const proc = Bun.spawn([bun, "install", "--no-cache", "--verbose"], {
cwd,
stdout: "pipe",
stderr: "pipe",
env: {
...process.env,
BUN_BE_BUN: "1",
BUN_INSTALL_CACHE_DIR: cache,
},
})
const [code, stdout, stderr] = await Promise.all([
proc.exited,
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
])
return { code, stdout, stderr }
}
const a = path.join(root, "a")
const b = path.join(root, "b")
await setup(a)
await setup(b)
await reset(a)
await reset(b)
await rm(cache, { recursive: true, force: true })
console.log("serial")
console.log((await run(a)).code, (await run(b)).code)
await reset(a)
await reset(b)
await rm(cache, { recursive: true, force: true })
console.log("parallel")
console.log(await Promise.all([run(a), run(b)]))The exact command that reproduced it here was:
bun tmp/bun-install-race.ts 3What is the expected behavior?
Both installs should succeed. --no-cache should not let concurrent installs invalidate a shared package cache entry another install is about to use.
What do you see instead?
One of the parallel installs can fail with:
ENOENT: failed opening cache/package/version dir for package @opencode-ai/sdkor:
ENOENT: failed opening cache/package/version dir for package zodObserved pattern here:
- serial with shared cache: passes
- parallel with shared cache: fails
- no lockfiles needed for the minimal case
Additional information
Likely Windows-only cache publish race.
--no-cacheonly disables manifest cache, not the package/install cache:src/install/PackageManager/PackageManagerOptions.zig:534- shared cache dir is still used normally:
src/install/PackageManager/PackageManagerDirectories.zig:121 - Windows cache publish path is in
src/install/extract_tarball.zig:354 - cache path is later resolved via
src/install/PackageManager/PackageManagerDirectories.zig:431 - install then opens the real cache dir in
src/install/PackageInstall.zig:506
The suspicious bit is the Windows collision handling in extract_tarball.zig: if another process already created the destination cache dir, Bun currently tries to move/delete/replace that existing cache entry. Another concurrent install can already have resolved the version link and then hit ENOENT when opening the real cache dir.
Locally, a self-contained regression test built around fixture packages reproduces this 10/10 on 1.3.10 and points at the same code path.