This is a minimal standalone reproduction of a libuv bug that surfaces in Bun-compiled executables on Windows: child_process.spawn({ stdio: "pipe" }) hangs forever when it's called from a Bun process running inside an AppContainer sandbox.
The root cause is in libuv's uv__pipe_server, which creates a named pipe in the global \Device\NamedPipe\ namespace and retries on ERROR_ACCESS_DENIED. That retry loop makes sense outside a sandbox, where the error is typically transient, but AppContainers are permanently denied access to the global pipe namespace — so the loop spins indefinitely. A fix needs to detect the AppContainer case and use the \\.\pipe\LOCAL\ namespace, which AppContainers are allowed to use.
launch-in-appcontainer.cpp— This is the host launcher, built with MSVC (cl). It creates an AppContainer profile, grants the sandbox read+execute access on the directory containing the child executable, spawns the child inside with its stdio inherited from the launcher, and cleans up the profile and the granted ACL on exit.repro.ts— This is the bug reproduction itself. It logsbefore, callschild_process.spawnwithstdio: "pipe", and then logsafter. When run outside a sandbox it prints both lines as expected; when run inside an AppContainer, it hangs indefinitely atbeforebecause libuv's named-pipe server retries forever on theERROR_ACCESS_DENIEDthat AppContainers return for the global pipe namespace.control.ts— This is a control test that also runs inside the sandbox: it logsstart, waits one second, and then logsend. Its job is to confirm that the launcher itself, the inherited stdio plumbing, and Bun's event loop all work correctly under AppContainer, which isolates the bug demonstrated byrepro.tstochild_process.spawnspecifically.
Needs bun on PATH and a Bun checkout (for scripts/vs-shell.ps1, which locates MSVC).
# Build the launcher. Substitute your local Bun checkout for path/to/bun.
pwsh -File path/to/bun/scripts/vs-shell.ps1 cl -nologo -W3 -EHsc launch-in-appcontainer.cpp
# Compile the Bun scripts into standalone exes.
bun build --compile control.ts --outfile control.exe
bun build --compile repro.ts --outfile repro.exe
# Control — sandboxed timer that prints "start" then "end" a second later.
# Confirms the launcher, stdio plumbing, and Bun's event loop all work inside
# an AppContainer. Should succeed.
./launch-in-appcontainer.exe ./control.exe
# Baseline — repro.exe outside the sandbox, prints "before" then "after".
./repro.exe
# Repro — sandboxed child_process.spawn hangs at "before". The launcher's 10s
# timeout terminates the child and exits 124.
./launch-in-appcontainer.exe ./repro.exe- Ninja PR #2354 — prior art for the same bug in Ninja's subprocess code; Microsoft reviewer recommended the
GetTokenInformation(TokenIsAppContainer)gating pattern used in the proposed fix. - CreateNamedPipeW — documents the
\\.\pipe\LOCAL\requirement for AppContainer. - Implementing an AppContainer — Win32 APIs used by
launch-in-appcontainer.cpp(CreateAppContainerProfile,PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES).