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:
- Write a
.bat file that starts node cliDaemon.js with stdout/stderr redirected to log files
- Write a
.vbs file that runs the .bat via WScript.Shell.Run (with False = don't wait)
- Execute
wscript launcher.vbs synchronously — it returns immediately after launching the .bat
- The daemon becomes an orphaned process (adopted by the system), fully outside the caller's process tree
- 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 (
.bat → cmd.exe → PowerShell → node 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
Bug
playwright-cli attach --cdp=<url>(andplaywright-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
Expected: command prints snapshot and returns immediately.
Actual: command prints snapshot but the calling process hangs forever.
Root Cause
Session.startDaemon()insession.jsuseschild_process.spawnwithdetached: trueto spawn the daemon process:On Windows,
detached: truesetsCREATE_NEW_PROCESS_GROUP | DETACHED_PROCESSflags. 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
detached: true+ long-lived childdetached: true+ child exits quicklydetached: false+unref()wscript.exeintermediate launcherThis 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.exeas an intermediate launcher to fully orphan the daemon process from the caller's process tree:.batfile that startsnode cliDaemon.jswith stdout/stderr redirected to log files.vbsfile that runs the.batviaWScript.Shell.Run(withFalse= don't wait)wscript launcher.vbssynchronously — it returns immediately after launching the.bat### Success\nDaemon listening on <path>\n<EOF>)On non-Windows platforms, the existing
detached: true+child.unref()approach works correctly.Implementation sketch
Environment
.bat→cmd.exe→PowerShell→nodeprocess chain)Impact
This affects any Windows user running
playwright-clifrom: