From 4b5ba9f245b35d0c78058df78e88bec843f4c332 Mon Sep 17 00:00:00 2001 From: Hung-Che Lo Date: Sat, 4 Apr 2026 23:29:04 +0800 Subject: [PATCH] fix(native): auto_launch() honours AGENT_BROWSER_PROVIDER for cloud providers (#1126) When a non-launch command (e.g. open, snapshot) triggers auto_launch() before the explicit launch command is processed, auto_launch() now checks AGENT_BROWSER_PROVIDER and connects via the provider API instead of always falling back to a local Chrome instance. Also redirects daemon stderr to /dev/null when not in debug mode to prevent crashes from broken pipe after the CLI drops the piped stderr handle. Cloud providers may write to stderr during connection setup. Fixes #1125 Related: #979 --- cli/src/native/actions.rs | 42 +++++++++++++++++++++++++++++++++++++++ cli/src/native/daemon.rs | 16 +++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/cli/src/native/actions.rs b/cli/src/native/actions.rs index c8f154d..6b2511c 100644 --- a/cli/src/native/actions.rs +++ b/cli/src/native/actions.rs @@ -1481,6 +1481,48 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> { return Ok(()); } + // Cloud provider: when AGENT_BROWSER_PROVIDER is set, connect via the + // provider API instead of launching a local Chrome instance. This mirrors + // the logic in handle_launch() so that auto_launch (triggered by any + // command arriving before an explicit "launch") honours the provider env. + if let Ok(provider) = env::var("AGENT_BROWSER_PROVIDER") { + let p = provider.to_lowercase(); + // ios/safari are device providers handled via explicit launch command + if !p.is_empty() && p != "ios" && p != "safari" { + let conn = providers::connect_provider(&p).await?; + let ws_headers = if p == "agentcore" { + providers::take_agentcore_ws_headers() + } else { + None + }; + let connect_result = if conn.direct_page { + BrowserManager::connect_cdp_direct(&conn.ws_url).await + } else if ws_headers.is_some() { + BrowserManager::connect_cdp_with_headers(&conn.ws_url, ws_headers).await + } else { + BrowserManager::connect_cdp(&conn.ws_url).await + }; + match connect_result { + Ok(mgr) => { + state.reset_input_state(); + state.browser = Some(mgr); + state.subscribe_to_browser_events(); + state.start_fetch_handler(); + state.start_dialog_handler(); + state.update_stream_client().await; + write_provider_file(&state.session_id, &p); + try_auto_restore_state(state).await; + return Ok(()); + } + Err(e) => { + if let Some(ref ps) = conn.session { + providers::close_provider_session(ps).await; + } + return Err(format!("Provider '{}' connection failed: {}", p, e)); + } + } + } + } let mgr = BrowserManager::launch(options, engine.as_deref()).await?; state.reset_input_state(); state.browser = Some(mgr); diff --git a/cli/src/native/daemon.rs b/cli/src/native/daemon.rs index 2aabed0..102a239 100644 --- a/cli/src/native/daemon.rs +++ b/cli/src/native/daemon.rs @@ -41,6 +41,22 @@ pub async fn run_daemon(session: &str) { session ); } + } else { + // Redirect stderr to /dev/null to prevent daemon crash when the + // parent CLI drops the piped stderr handle after startup. Cloud + // providers (AgentCore, Browserbase, etc.) may write to stderr + // during connection setup; a broken pipe would kill the daemon. + #[cfg(unix)] + { + use std::os::unix::io::IntoRawFd; + if let Ok(devnull) = fs::File::create("/dev/null") { + let fd = devnull.into_raw_fd(); + unsafe { + libc::dup2(fd, 2); + libc::close(fd); + } + } + } } let pid_path = socket_dir.join(format!("{}.pid", session));