From f8eb38c7f141c99d2e7668b1693d2c44bc3b9ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 17 Mar 2026 17:29:01 +0100 Subject: [PATCH] fix: use socket connectivity alone instead combining it with PID check for daemon liveness (#879) --- .changeset/fix-pid-namespace.md | 5 ++++ cli/src/connection.rs | 42 ++++----------------------------- 2 files changed, 9 insertions(+), 38 deletions(-) create mode 100644 .changeset/fix-pid-namespace.md diff --git a/.changeset/fix-pid-namespace.md b/.changeset/fix-pid-namespace.md new file mode 100644 index 0000000..2cb5576 --- /dev/null +++ b/.changeset/fix-pid-namespace.md @@ -0,0 +1,5 @@ +--- +"agent-browser": patch +--- + +Fix daemon detection for PID namespace isolation (e.g. `unshare`). Use socket connectivity as the sole liveness check instead of `kill(pid, 0)`, which fails when the caller cannot see the daemon's PID. diff --git a/cli/src/connection.rs b/cli/src/connection.rs index 82cb722..9665140 100644 --- a/cli/src/connection.rs +++ b/cli/src/connection.rs @@ -150,42 +150,6 @@ fn get_port_for_session(session: &str) -> u16 { 49152 + ((hash.unsigned_abs() as u32 % 16383) as u16) } -#[cfg(unix)] -fn is_daemon_running(session: &str) -> bool { - let pid_path = get_pid_path(session); - if !pid_path.exists() { - return false; - } - if let Ok(pid_str) = fs::read_to_string(&pid_path) { - if let Ok(pid) = pid_str.trim().parse::() { - unsafe { - if libc::kill(pid, 0) == 0 { - return true; - } - // EPERM means the process exists but we lack permission to - // signal it (e.g. inside a macOS sandbox). Only ESRCH means - // the process is genuinely gone. - return std::io::Error::last_os_error().raw_os_error() != Some(libc::ESRCH); - } - } - } - false -} - -#[cfg(windows)] -fn is_daemon_running(session: &str) -> bool { - let pid_path = get_pid_path(session); - if !pid_path.exists() { - return false; - } - let port = get_port_for_session(session); - TcpStream::connect_timeout( - &format!("127.0.0.1:{}", port).parse().unwrap(), - Duration::from_millis(100), - ) - .is_ok() -} - fn daemon_ready(session: &str) -> bool { #[cfg(unix)] { @@ -315,8 +279,10 @@ fn apply_daemon_env(cmd: &mut Command, session: &str, opts: &DaemonOptions) { } pub fn ensure_daemon(session: &str, opts: &DaemonOptions) -> Result { - // Check if daemon is running AND responsive - if is_daemon_running(session) && daemon_ready(session) { + // Socket connectivity is the sole liveness check — no PID check — so + // callers in a different PID namespace (e.g. unshare) can still reuse + // an existing daemon they can reach over the socket. + if daemon_ready(session) { // Double-check it's actually responsive by waiting and checking again // This handles the race condition where daemon is shutting down // (daemon has a 100ms shutdown delay, so we wait longer)