Repository navigation
Description
What version of Bun is running?
1.4.0-canary.1+7069b3092 (also reproduces on 1.3.14+0d9b296af)
What platform is your computer?
Microsoft Windows NT 10.0.26200.0 x64
What steps can reproduce the bug?
Complete, runnable reproduction: https://github.com/BlackHole1/bun-detached-windows-bug
(pwsh run.ps1 runs it and reports whether each spawned helper survived the parent's exit; includes a passing cmd /c start workaround variant and a multi-run stability check.)
The bug in one file — on Windows, a child spawned with Bun.spawn({ detached: true }) + unref() is killed the instant the parent Bun process exits, instead of outliving it. The same happens with node:child_process.spawn(..., { detached: true }) (Bun implements it on the
same primitive). The helper below waits for this (parent) process to exit, then writes marker.txt — a correctly-detached child should outlive the parent and create the file:
const parentPid = process.pid;
Bun.spawn({
cmd: [
"powershell", "-NoProfile", "-Command",
`Wait-Process -Id ${parentPid} -ErrorAction SilentlyContinue; Set-Content -LiteralPath marker.txt -Value ran`,
],
detached: true,
stdin: "ignore",
stdout: "ignore",
stderr: "ignore",
windowsHide: true,
}).unref();
console.log(`parent pid=${parentPid} spawned detached helper; exiting now`);Remove-Item marker.txt -ErrorAction SilentlyContinue
bun repro.ts
Start-Sleep -Seconds 2
Test-Path marker.txt # False (bug) — expected TrueWhat is the expected behavior?
marker.txt is created — the detached helper outlives the parent and runs after it exits. This matches the documentation, which states detached "allow[s] the child to outlive the parent".
What do you see instead?
marker.txt is never created. The helper is terminated the moment the parent exits, so it never runs its post-exit work. It is fully deterministic — 0/3 over repeated runs on each build, on both stable (1.3.14) and canary (1.4.0-canary.1).
Additional information
The child appears to be a member of a kill-on-close job object that Bun uses to manage child processes. On Windows, detached maps to UV_PROCESS_DETACHED (which detaches the console / process group) but does not add CREATE_BREAKAWAY_FROM_JOB, so the child stays in the
job and is killed when the parent (the job handle owner) exits. This makes it impossible to launch a post-exit helper (self-update / self-uninstall / any cleanup that must run after the launching process is gone).
Same failure mode as nodejs/node#51018 on Windows.
Workaround (verified, survives 3/3 on both builds): launch through a cmd /c start "" /b trampoline, which creates the helper as a job-breakaway process. Await the bootstrap cmd so start has launched the helper before exiting:
const boot = Bun.spawn({
cmd: [
"cmd", "/c", "start", "", "/b",
"powershell", "-NoProfile", "-Command",
`Wait-Process -Id ${process.pid} -ErrorAction SilentlyContinue; Set-Content -LiteralPath marker.txt -Value ran`,
],
stdin: "ignore",
stdout: "ignore",
stderr: "ignore",
windowsHide: true,
});
await boot.exited;
Activity
fix(uninstall): spawn windows cleanup helper via cmd start trampoline (…
tvvignesh commentedon May 30, 2026
I see this behavior as well in windows - works well in mac and linux. Had to use
node:child_processcause of this to workaroundBlackHole1 commentedon Jun 1, 2026
@tvvignesh I remember node:child_process doesn't seem to work either, because in my impression, in Bun, node:child_process.spawn is actually a wrapper around Bun.spawn.
BlackHole1 commentedon Jun 1, 2026
@robobun Please try to reproduce and fix
robobun commentedon Sep 11, 2026
I looked at this while I worked on #42272. The job object is not the cause here. The helper is not killed:
powershell.exegives up at startup, becausedetached: truegives it no console.Measured on Windows Server 2019 x64 (Windows PowerShell 5.1), current
main:detached: exit code 5,marker.txtexists,ps.loghashello.detached: true: exit code 0 after about 40 ms, nomarker.txt,ps.logis empty. The parent is still alive at that point, so nothing killed the child.On Windows,
detachedmaps to libuv'sUV_PROCESS_DETACHED, which passesDETACHED_PROCESStoCreateProcessW. The child has no console at all, and Windows PowerShell does not run a command without one. libuv does skip its kill-on-close job object for a detached child, so a program that can run without a console (abunornodescript, for example) does outlive the parent. Thecmd /c start "" /bworkaround works becausecmd.exegets a hidden console (windowsHidewith no inherited stdio), PowerShell attaches to that console, and libuv's job does not extend to grandchildren.#42272 adds an opt-in
windowsKeepAlivespawn option for this case: the child gets a new console that has no window and is not put in the job object. Your repro (helper waits for the parent to exit, then writes the marker) writes the marker withwindowsKeepAlive: truein place ofdetached: true. It does not change whatdetached: truedoes.