Bug: [Windows] CLI hangs when PowerShell captures output after close-then-open sequence
Description
On Windows, when using PowerShell to capture CLI output (via $output = & agent-browser ... or 2>&1 redirection), the CLI process hangs and does not return an exit code after executing close followed by open . The process only exits after running another close command.
This does not affect manual execution (without output capture), and does not occur on macOS or Linux.
Reproduction
# Step 1: Open a page (works fine)
$output = & agent-browser open --headed "https://example.com"
# Step 2: Close the session
$output = & agent-browser close
# Step 3: Open again — HANGS here, never returns
$output = & agent-browser open --headed "https://example.com"
# Only returns after running: agent-browser close
The same issue occurs with 2>&1 redirection:
agent-browser close
agent-browser open --headed "https://example.com" 2>&1
# HANGS
Expected Behavior
The open command should exit and return control to PowerShell immediately after the daemon is launched, regardless of whether output is being captured.
Actual Behavior
The CLI process hangs indefinitely. PowerShell waits for all write ends of its output pipe to close before considering output complete. The daemon child process holds an inherited copy of the pipe write-end handle, preventing PowerShell from detecting that output is finished.
Root Cause
When the CLI spawns the daemon process via Rust's std::process::Command::spawn() , the Windows implementation defaults to bInheritHandles = TRUE in the underlying CreateProcessW call. This causes the daemon to inherit all inheritable handles from the parent CLI process, including:
- The stdout pipe handle created by PowerShell for $output = capture
- The stderr pipe handle created by PowerShell for 2>&1 redirection After the CLI exits (closing its own pipe handles), the daemon still holds the inherited pipe write-end handle. PowerShell's output capture waits for all write ends to close, causing the hang.
This only manifests after close then open because the first open starts a new daemon, close kills it, and the second open spawns a new daemon that inherits the current PowerShell session's pipe handles.
Proposed Fix
Use the Windows CreateProcessW API directly in cli/src/connection.rs to spawn the daemon with bInheritHandles = FALSE , preventing the daemon from inheriting any handles from the parent process.
Key changes:
- New spawn_daemon_no_inherit function : Calls CreateProcessW directly with bInheritHandles = FALSE and explicit creation flags ( CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS | CREATE_BREAKAWAY_FROM_JOB | CREATE_UNICODE_ENVIRONMENT ).
- New build_env_block function : Constructs a complete environment block (parent env vars + custom vars like AGENT_BROWSER_DAEMON=1 ), since passing a non-null lpEnvironment means the child only uses the provided variables.
- New WindowsDaemonChild struct : Replaces std::process::Child on Windows. Only holds the process handle for detecting early daemon exit, without any pipe handles.
- Modified ensure_daemon function : Uses spawn_daemon_no_inherit instead of Command::spawn() on Windows.
Environment
- OS : Windows (Server 2022, also reproducible on Windows 10/11)
- agent-browser version : 0.27.0
- Rust : 1.96.0 (stable)
- PowerShell : 5.1 (also affects PowerShell 7.x)
Source: vercel-labs/agent-browser