From c4f0f22ae91cb2466827b64dd1aca16299a6e031 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 14 Mar 2026 18:08:22 -0500 Subject: [PATCH] fix: prevent daemon panic on broken stderr pipe during Chrome launch (#802) Replace all `eprintln!` calls in daemon-context code with `let _ = writeln!(std::io::stderr(), ...)` so that broken pipe errors on stderr are silently ignored instead of panicking. The CLI client spawns the daemon with piped stderr to capture startup errors, then drops the pipe handle once the daemon is ready. Any subsequent `eprintln!` in the daemon panics because Rust's `eprintln!` macro internally unwraps the write result. This caused the reported "failed printing to stderr: Broken pipe (os error 32)" panic during Chrome launch on Linux. Closes #799 Co-authored-by: ctate <366502+ctate@users.noreply.github.com> --- cli/src/native/actions.rs | 3 ++- cli/src/native/auth.rs | 4 +++- cli/src/native/cdp/chrome.rs | 15 +++++++++++---- cli/src/native/daemon.rs | 24 +++++++++++++++--------- cli/src/native/inspect_server.rs | 6 ++++-- 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/cli/src/native/actions.rs b/cli/src/native/actions.rs index 2887853..40ab436 100644 --- a/cli/src/native/actions.rs +++ b/cli/src/native/actions.rs @@ -1,5 +1,6 @@ use serde_json::{json, Value}; use std::env; +use std::io::Write; use std::sync::Arc; use tokio::sync::{broadcast, RwLock}; @@ -1247,7 +1248,7 @@ fn open_url_in_browser(url: &str) { "unsupported platform", )); if let Err(e) = result { - eprintln!("[inspect] Failed to open browser: {}", e); + let _ = writeln!(std::io::stderr(), "[inspect] Failed to open browser: {}", e); } } diff --git a/cli/src/native/auth.rs b/cli/src/native/auth.rs index 20af27c..c157761 100644 --- a/cli/src/native/auth.rs +++ b/cli/src/native/auth.rs @@ -3,6 +3,7 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::fs; +use std::io::Write; use std::path::PathBuf; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -137,7 +138,8 @@ fn ensure_encryption_key() -> Result, String> { let _ = fs::set_permissions(&key_file, fs::Permissions::from_mode(0o600)); } - eprintln!( + let _ = writeln!( + std::io::stderr(), "[agent-browser] Auto-generated encryption key at {} -- back up this file or set {}", key_file.display(), ENCRYPTION_KEY_ENV diff --git a/cli/src/native/cdp/chrome.rs b/cli/src/native/cdp/chrome.rs index d810aec..b6128a0 100644 --- a/cli/src/native/cdp/chrome.rs +++ b/cli/src/native/cdp/chrome.rs @@ -1,4 +1,4 @@ -use std::io::{BufRead, BufReader}; +use std::io::{BufRead, BufReader, Write}; use std::path::{Path, PathBuf}; use std::process::{Child, Command, Stdio}; use std::time::Duration; @@ -47,7 +47,10 @@ impl Drop for ChromeProcess { std::thread::sleep(Duration::from_millis(100)); } Err(e) => { - eprintln!( + // Use write! instead of eprintln! to avoid panicking + // if the daemon's stderr pipe is broken (parent dropped it). + let _ = writeln!( + std::io::stderr(), "Warning: failed to clean up temp profile {}: {}", dir.display(), e @@ -207,9 +210,13 @@ pub fn launch_chrome(options: &LaunchOptions) -> Result { Err(e) => { last_err = e; if attempt < max_attempts { - eprintln!( + // Use write! instead of eprintln! to avoid panicking + // if the daemon's stderr pipe is broken (parent dropped it). + let _ = writeln!( + std::io::stderr(), "[chrome] Launch attempt {}/{} failed, retrying in 500ms...", - attempt, max_attempts + attempt, + max_attempts ); std::thread::sleep(Duration::from_millis(500)); } diff --git a/cli/src/native/daemon.rs b/cli/src/native/daemon.rs index 65d3918..ab5ba71 100644 --- a/cli/src/native/daemon.rs +++ b/cli/src/native/daemon.rs @@ -1,6 +1,7 @@ use serde_json::Value; use std::env; use std::fs; +use std::io::Write; use std::path::PathBuf; use std::process; use std::sync::Arc; @@ -47,11 +48,12 @@ pub async fn run_daemon(session: &str) { stream_client = Some(client_slot.clone()); let stream_path = socket_dir.join(format!("{}.stream", session)); if let Err(e) = fs::write(&stream_path, stream_server.port().to_string()) { - eprintln!("Failed to write .stream file: {}", e); + let _ = + writeln!(std::io::stderr(), "Failed to write .stream file: {}", e); } } Err(e) => { - eprintln!("Stream server failed to start: {}", e); + let _ = writeln!(std::io::stderr(), "Stream server failed to start: {}", e); } } } @@ -73,7 +75,7 @@ pub async fn run_daemon(session: &str) { let _ = fs::remove_file(&stream_path); if let Err(e) = result { - eprintln!("Daemon error: {}", e); + let _ = writeln!(std::io::stderr(), "Daemon error: {}", e); process::exit(1); } } @@ -112,7 +114,7 @@ async fn run_socket_server( }); } Err(e) => { - eprintln!("Accept error: {}", e); + let _ = writeln!(std::io::stderr(), "Accept error: {}", e); } } } @@ -185,7 +187,7 @@ async fn run_socket_server( }); } Err(e) => { - eprintln!("Accept error: {}", e); + let _ = writeln!(std::io::stderr(), "Accept error: {}", e); } } } @@ -299,21 +301,25 @@ async fn shutdown_signal() { let mut sigint = match signal::unix::signal(signal::unix::SignalKind::interrupt()) { Ok(s) => s, Err(e) => { - eprintln!("Failed to install SIGINT handler: {}", e); + let _ = writeln!(std::io::stderr(), "Failed to install SIGINT handler: {}", e); process::exit(1); } }; let mut sigterm = match signal::unix::signal(signal::unix::SignalKind::terminate()) { Ok(s) => s, Err(e) => { - eprintln!("Failed to install SIGTERM handler: {}", e); + let _ = writeln!( + std::io::stderr(), + "Failed to install SIGTERM handler: {}", + e + ); process::exit(1); } }; let mut sighup = match signal::unix::signal(signal::unix::SignalKind::hangup()) { Ok(s) => s, Err(e) => { - eprintln!("Failed to install SIGHUP handler: {}", e); + let _ = writeln!(std::io::stderr(), "Failed to install SIGHUP handler: {}", e); process::exit(1); } }; @@ -328,7 +334,7 @@ async fn shutdown_signal() { #[cfg(windows)] { if let Err(e) = signal::ctrl_c().await { - eprintln!("Failed to install Ctrl+C handler: {}", e); + let _ = writeln!(std::io::stderr(), "Failed to install Ctrl+C handler: {}", e); process::exit(1); } } diff --git a/cli/src/native/inspect_server.rs b/cli/src/native/inspect_server.rs index 518c2ad..1c151a4 100644 --- a/cli/src/native/inspect_server.rs +++ b/cli/src/native/inspect_server.rs @@ -1,3 +1,4 @@ +use std::io::Write; use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::Arc; @@ -87,7 +88,7 @@ async fn accept_loop( tokio::spawn(async move { if let Err(e) = handle_connection(stream, proxy, tid, chp, proxy_port).await { - eprintln!("[inspect] connection error: {}", e); + let _ = writeln!(std::io::stderr(), "[inspect] connection error: {}", e); } }); } @@ -233,7 +234,8 @@ async fn handle_ws_proxy( let raw_msg = match raw_rx.recv().await { Ok(msg) => msg, Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => { - eprintln!( + let _ = writeln!( + std::io::stderr(), "[inspect] warning: dropped {} CDP messages (channel lag)", n );