Skip to content

playwright-cli attach hangs indefinitely on Windows when called from process managers (opencode bash tool, CI) #416

@skywalker-35

Description

@skywalker-35

Bug

playwright-cli attach --cdp=<url> (and playwright-cli open) hangs indefinitely on Windows when called from process managers that track child process trees — e.g., opencode's bash tool, CI runners, or any tool that uses Job Objects / process tree tracking.

The command output is printed correctly (snapshot data appears), but the calling process never returns. The user must manually press ESC/kill the process.

Reproduction

# Start a browser with CDP
msedge --remote-debugging-port=9222

# In another terminal, run playwright-cli attach via a process manager:
opencode  # then in bash tool: playwright-cli attach --cdp=http://localhost:9222
# OR via cmd.exe chain: cmd /c "node playwright-cli.js attach --cdp=http://localhost:9222"

Expected: command prints snapshot and returns immediately.
Actual: command prints snapshot but the calling process hangs forever.

Root Cause

Session.startDaemon() in session.js uses child_process.spawn with detached: true to spawn the daemon process:

const child = spawn(process.execPath, args, {
  detached: true,        // CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS
  stdio: ["ignore", "pipe", err],
});
child.unref();

On Windows, detached: true sets CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS flags. However, this is NOT sufficient to fully detach the child from the parent's process tree. Process managers that track child process trees (via Job Objects or process enumeration) still see the daemon as a descendant of the parent process.

Since the daemon is long-lived (it runs until explicitly stopped), the calling process waits indefinitely for "all child processes to exit" — even after the Node.js parent has called process.exit(0).

Key experimental findings

Scenario Calling process hangs? Daemon survives?
detached: true + long-lived child ✅ Hangs ✅ Survives
detached: true + child exits quickly ❌ Returns N/A
detached: false + unref() ❌ Returns ❌ Killed with parent
wscript.exe intermediate launcher ❌ Returns ✅ Survives

This confirms the issue is specifically about long-lived detached children remaining in the parent's process tree on Windows.

Suggested Fix

On Windows, use wscript.exe as an intermediate launcher to fully orphan the daemon process from the caller's process tree:

  1. Write a .bat file that starts node cliDaemon.js with stdout/stderr redirected to log files
  2. Write a .vbs file that runs the .bat via WScript.Shell.Run (with False = don't wait)
  3. Execute wscript launcher.vbs synchronously — it returns immediately after launching the .bat
  4. The daemon becomes an orphaned process (adopted by the system), fully outside the caller's process tree
  5. Poll the stdout log file for the daemon handshake (### Success\nDaemon listening on <path>\n<EOF>)

On non-Windows platforms, the existing detached: true + child.unref() approach works correctly.

Implementation sketch

if (isWindows) {
  // Write .bat launcher with output redirection
  const batContent = `@echo off\r\n"${nodeExe}" ${args} > "${outLog}" 2> "${errLog}"\r\n`;
  fs.writeFileSync(batPath, batContent);
  
  // Write .vbs that runs .bat asynchronously
  const vbsContent = `Set objShell = CreateObject("WScript.Shell")\r\nobjShell.Run """${batPath}""", 0, False\r\n`;
  fs.writeFileSync(vbsPath, vbsContent);
  
  // wscript returns immediately; daemon is fully detached
  execSync(`wscript "${vbsPath}"`, { stdio: "ignore" });
} else {
  // Original behavior for macOS/Linux
  const child = spawn(process.execPath, args, {
    detached: true,
    stdio: ["ignore", "pipe", err],
  });
  child.unref();
}

Environment

  • OS: Windows 11
  • Node.js: v22.22.2
  • @playwright/cli: 0.1.13
  • playwright-core: (bundled)
  • Caller: opencode bash tool (.batcmd.exePowerShellnode process chain)

Impact

This affects any Windows user running playwright-cli from:

  • AI coding agents (opencode, Claude Code, etc.) that use process-managed bash tools
  • CI/CD pipelines with process tree tracking
  • Any tool that monitors child processes via Job Objects

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions