Skip to content

Bash tool hangs on Windows when command spawns detached child processes (e.g. playwright-cli) #24731

Closed as not planned
mdev34-lab/silvercode
#1

Description

@disrei

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

  1. Install playwright-cli: npm install -g @playwright/cli@latest
  2. Run in OpenCode on Windows:
    playwright-cli open https://example.com
    
  3. 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)
    
  4. 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-all work 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

  1. Use CREATE_BREAKAWAY_FROM_JOB process creation flag on Windows when spawning commands, so child processes can detach from the Job Object.
  2. Detect process exit and return immediately, rather than waiting for all handles in the process group to close.
  3. 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

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Apr 28, 2026
  2. github-actions commented on Apr 28, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    • bash tool hangs when command spawns background child processes #20902: Same root cause — bash tool hangs when a command spawns background child processes that inherit stdio pipe descriptors. That issue covers Linux/WSL2 but the underlying mechanism is the same. The Windows-specific Job Object behavior you describe is an additional angle worth noting there.
  3. disrei commented on Apr 28, 2026

    @disrei
    Author

    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' and unref(). The parent process prints done and exits immediately, but the Bash tool never returns - it waits until the timeout is reached.

    I also tested:

    • stdio: ['ignore', 'pipe', 'ignore'] with child.stdout.destroy() - still hangs
    • stdio: ['ignore', fd, 'ignore'] with file descriptor to NUL - still hangs
    • stdio: 'ignore' - still hangs

    Conclusion: 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 a detached child process will trigger this behavior.

    A simple Start-Process notepad in PowerShell does NOT hang, because Start-Process creates a new process outside the current Job Object. The issue is specific to Node.js child_process.spawn() with detached: true, where the child inherits the Job Object on Windows despite detached being set.

  4. disrei commented on Apr 28, 2026

    @disrei
    Author

    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: true and unref().

    Proof

    Using a test script that spawns a detached child with a known timeout:

    const { spawn } = require('child_process');
    const c = spawn(process.execPath, ['-e', 'setTimeout(()=>{},N)'], { detached: true, stdio: 'ignore' });
    c.unref();
    console.log('parent done');
    Child timeout PowerShell itself OpenCode Bash tool returns after
    No child 0.04s Immediately
    3s 0.05s ~3s
    5s 0.05s ~5s
    10s 0.05s ~10s
    Infinite (daemon) 0.05s Never

    PowerShell completes in ~0.05s every time. But OpenCode adds wait time exactly equal to the child process lifetime.

    This confirms:

    1. PowerShell is NOT the one waiting - it exits in 0.05s
    2. OpenCode's process management layer is waiting for the entire process tree
    3. For playwright-cli, the daemon process runs indefinitely, so OpenCode hangs forever

    This is likely caused by Go's os/exec on Windows using Job Objects that track all descendant processes. The fix would be to either:

    • Not use Job Objects, or
    • Use CREATE_BREAKAWAY_FROM_JOB when spawning processes, or
    • Only wait for the direct child process exit, not the entire Job Object
  5. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  6. added a commit that references this issue on Jun 12, 2026
    91da44f
  7. github-actions commented on Jul 4, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

  8. yhao121 commented on Jul 11, 2026

    @yhao121

    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 exit or close event, instead of waiting only for close, 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.

  9. added a commit that references this issue on Aug 18, 2026
    49ef234
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

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