Repository navigation
Bash tool hangs on Windows when command spawns detached child processes (e.g. playwright-cli) #24731
Description
Description
On Windows, the Bash tool never returns when executing commands that spawn detached background (daemon) child processes, even though the main process has already exited and all output has been written to stdout.
This affects tools like playwright-cli (from @playwright/cli), which spawns a detached daemon process via child_process.spawn() with detached: true and unref(). The main playwright-cli process completes in ~4 seconds and calls process.exit(0), but the Bash tool continues to wait indefinitely until the user manually cancels it.
Steps to Reproduce
- Install playwright-cli:
npm install -g @playwright/cli@latest - Run in OpenCode on Windows:
playwright-cli open https://example.com - The command output appears immediately (within ~4 seconds):
### Browser default opened with pid 23536. ### Ran Playwright code await page.goto('https://example.com'); ### Page - Page URL: https://example.com/ - Page Title: Example Domain ### Snapshot - [Snapshot](.playwright-cli\page-....yml) - But the Bash tool never returns. The agent cannot proceed to the next step. The user must manually cancel the tool execution.
Investigation
I spent significant time debugging this and confirmed:
- The main process exits correctly. Added debug logging showing program() resolves and process.exit(0) is called within ~4 seconds.
- The daemon child process is the blocker. playwright-cli spawns a daemon via spawn(detached: true, stdio: [...]) + child.unref(). This daemon stays alive to serve subsequent CLI commands.
- On Windows, PowerShell / the process manager waits for ALL processes in the process group/Job Object to exit, not just the main process. The detached daemon inherits the Job Object membership, so the Bash tool waits forever.
- Other playwright-cli commands like
close,list,kill-allwork fine because they don't spawn long-lived child processes. - Attempted fixes that did NOT help:
- Replacing stdout pipe with file-based IPC in playwright-cli
- Adding
process.exit(0)to the entry point - Wrapping with
cmd /c,Start-Process,Start-Job - All of these still result in the Bash tool hanging
Root Cause
This is a Windows-specific behavior. On Linux/macOS, detached: true + unref() is sufficient to fully detach a child process from its parent. On Windows, child processes inherit the Job Object of their parent, and the process management layer (used by OpenCode's Bash tool) waits for all processes in the Job Object to terminate.
Expected Behavior
The Bash tool should return as soon as the main process exits, regardless of whether detached child processes are still running.
Possible Solutions
- Use
CREATE_BREAKAWAY_FROM_JOBprocess creation flag on Windows when spawning commands, so child processes can detach from the Job Object. - Detect process exit and return immediately, rather than waiting for all handles in the process group to close.
- Add a configuration option to control whether the Bash tool waits for child processes.
Environment
- OS: Windows 11 (win32)
- Shell: PowerShell 5.1
- Node.js: v24.14.0
- playwright-cli: v0.1.9 (
@playwright/cli) - OpenCode: latest
Activity
github-actions commentedon Apr 28, 2026
This issue might be a duplicate of existing issues. Please check:
disrei commentedon Apr 28, 2026
Minimal Reproduction (no playwright-cli needed)
After further investigation, this issue can be reproduced with a simple Node.js one-liner that has nothing to do with playwright-cli:
node -e "const{spawn}=require('child_process');const c=spawn(process.execPath,['-e','setTimeout(()=>{},60000)'],{detached:true,stdio:'ignore'});c.unref();console.log('done')"This spawns a detached child process with
stdio: 'ignore'andunref(). The parent process printsdoneand exits immediately, but the Bash tool never returns - it waits until the timeout is reached.I also tested:
stdio: ['ignore', 'pipe', 'ignore']withchild.stdout.destroy()- still hangsstdio: ['ignore', fd, 'ignore']with file descriptor to NUL - still hangsstdio: 'ignore'- still hangsConclusion: The OpenCode Bash tool waits for ALL processes in the process group/Job Object to terminate on Windows, regardless of stdio configuration. This is not specific to playwright-cli; any command that
spawns adetachedchild process will trigger this behavior.A simple
Start-Process notepadin PowerShell does NOT hang, becauseStart-Processcreates a new process outside the current Job Object. The issue is specific to Node.jschild_process.spawn()withdetached: true, where the child inherits the Job Object on Windows despitedetachedbeing set.disrei commentedon Apr 28, 2026
Confirmed: OpenCode waits for detached child process to exit
Further testing confirms that OpenCode's Bash tool waits for ALL child processes in the process tree to exit, even when they are spawned with
detached: trueandunref().Proof
Using a test script that spawns a detached child with a known timeout:
PowerShell completes in ~0.05s every time. But OpenCode adds wait time exactly equal to the child process lifetime.
This confirms:
playwright-cli, the daemon process runs indefinitely, so OpenCode hangs foreverThis is likely caused by Go's
os/execon Windows using Job Objects that track all descendant processes. The fix would be to either:CREATE_BREAKAWAY_FROM_JOBwhen spawning processes, orfix(core): resolve spawn completion on exit, not only close
github-actions commentedon Jul 4, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.
yhao121 commentedon Jul 11, 2026
This appears to be the same root cause as #24731.
The browser opens successfully and Playwright finishes producing its output, but OpenCode continues waiting indefinitely and the agent cannot proceed.
The proposed fix in #29831 addresses this by resolving the spawned process on the first
exitorcloseevent, instead of waiting only forclose, which may never fire when a detached child process keeps inherited stdio open.I can still reproduce this consistently on Windows 11 with OpenCode 1.17.18, while the same Playwright CLI workflow completes normally in Claude Code.
fix(core): handle detached child processes holding stdio open