fix: use ~/.agent-browser for socket files instead of TMPDIR (#180)
* fix: use ~/.agent-browser for socket files instead of TMPDIR This fixes issue #163 where different TMPDIR values (common with tmux/screen/VSCode/IntelliJ) caused the CLI and daemon to use different socket paths. Socket directory priority: 1. AGENT_BROWSER_SOCKET_DIR (explicit override) 2. $XDG_RUNTIME_DIR/agent-browser (Linux standard) 3. ~/.agent-browser (fallback, like Docker Desktop) Both CLI (Rust) and daemon (Node.js) now use the same logic. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: session list now looks in correct socket directory - Make get_socket_dir() public in connection.rs - Update session list to use get_socket_dir() instead of temp_dir() - Update pid file pattern from agent-browser-{session}.pid to {session}.pid - Add tmpdir fallback to daemon.ts when homedir is unavailable Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: add unit tests for socket directory resolution Add comprehensive tests for get_socket_dir/getSocketDir to verify: - AGENT_BROWSER_SOCKET_DIR takes priority - Empty strings are ignored (fixes Rust/TypeScript consistency) - XDG_RUNTIME_DIR fallback works correctly - Home directory fallback when env vars unset Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
cb37630ccf
commit
946d236d9f
+124
-6
@@ -81,21 +81,44 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the base directory for socket/pid files.
|
||||
/// Priority: AGENT_BROWSER_SOCKET_DIR > XDG_RUNTIME_DIR > ~/.agent-browser > tmpdir
|
||||
pub fn get_socket_dir() -> PathBuf {
|
||||
// 1. Explicit override (ignore empty string)
|
||||
if let Ok(dir) = env::var("AGENT_BROWSER_SOCKET_DIR") {
|
||||
if !dir.is_empty() {
|
||||
return PathBuf::from(dir);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. XDG_RUNTIME_DIR (Linux standard, ignore empty string)
|
||||
if let Ok(runtime_dir) = env::var("XDG_RUNTIME_DIR") {
|
||||
if !runtime_dir.is_empty() {
|
||||
return PathBuf::from(runtime_dir).join("agent-browser");
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Home directory fallback (like Docker Desktop's ~/.docker/run/)
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
return home.join(".agent-browser");
|
||||
}
|
||||
|
||||
// 4. Last resort: temp dir
|
||||
env::temp_dir().join("agent-browser")
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_socket_path(session: &str) -> PathBuf {
|
||||
let tmp = env::temp_dir();
|
||||
tmp.join(format!("agent-browser-{}.sock", session))
|
||||
get_socket_dir().join(format!("{}.sock", session))
|
||||
}
|
||||
|
||||
fn get_pid_path(session: &str) -> PathBuf {
|
||||
let tmp = env::temp_dir();
|
||||
tmp.join(format!("agent-browser-{}.pid", session))
|
||||
get_socket_dir().join(format!("{}.pid", session))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_port_path(session: &str) -> PathBuf {
|
||||
let tmp = env::temp_dir();
|
||||
tmp.join(format!("agent-browser-{}.port", session))
|
||||
get_socket_dir().join(format!("{}.port", session))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -178,6 +201,12 @@ pub fn ensure_daemon(
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure socket directory exists
|
||||
let socket_dir = get_socket_dir();
|
||||
if !socket_dir.exists() {
|
||||
fs::create_dir_all(&socket_dir).map_err(|e| format!("Failed to create socket directory: {}", e))?;
|
||||
}
|
||||
|
||||
let exe_path = env::current_exe().map_err(|e| e.to_string())?;
|
||||
let exe_dir = exe_path.parent().unwrap();
|
||||
|
||||
@@ -354,3 +383,92 @@ pub fn send_command(cmd: Value, session: &str) -> Result<Response, String> {
|
||||
|
||||
serde_json::from_str(&response_line).map_err(|e| format!("Invalid response: {}", e))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
// Mutex to prevent parallel tests from interfering with env vars
|
||||
static ENV_MUTEX: Mutex<()> = Mutex::new(());
|
||||
|
||||
/// RAII guard that locks env mutex and restores env vars on drop
|
||||
struct EnvGuard<'a> {
|
||||
_lock: MutexGuard<'a, ()>,
|
||||
vars: Vec<(String, Option<String>)>,
|
||||
}
|
||||
|
||||
impl<'a> EnvGuard<'a> {
|
||||
fn new(var_names: &[&str]) -> Self {
|
||||
let lock = ENV_MUTEX.lock().unwrap();
|
||||
let vars = var_names
|
||||
.iter()
|
||||
.map(|&name| (name.to_string(), env::var(name).ok()))
|
||||
.collect();
|
||||
Self { _lock: lock, vars }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EnvGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
for (name, value) in &self.vars {
|
||||
match value {
|
||||
Some(v) => env::set_var(name, v),
|
||||
None => env::remove_var(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_socket_dir_explicit_override() {
|
||||
let _guard = EnvGuard::new(&["AGENT_BROWSER_SOCKET_DIR", "XDG_RUNTIME_DIR"]);
|
||||
|
||||
env::set_var("AGENT_BROWSER_SOCKET_DIR", "/custom/socket/path");
|
||||
env::remove_var("XDG_RUNTIME_DIR");
|
||||
|
||||
assert_eq!(get_socket_dir(), PathBuf::from("/custom/socket/path"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_socket_dir_ignores_empty_socket_dir() {
|
||||
let _guard = EnvGuard::new(&["AGENT_BROWSER_SOCKET_DIR", "XDG_RUNTIME_DIR"]);
|
||||
|
||||
env::set_var("AGENT_BROWSER_SOCKET_DIR", "");
|
||||
env::remove_var("XDG_RUNTIME_DIR");
|
||||
|
||||
assert!(get_socket_dir().to_string_lossy().ends_with(".agent-browser"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_socket_dir_xdg_runtime() {
|
||||
let _guard = EnvGuard::new(&["AGENT_BROWSER_SOCKET_DIR", "XDG_RUNTIME_DIR"]);
|
||||
|
||||
env::remove_var("AGENT_BROWSER_SOCKET_DIR");
|
||||
env::set_var("XDG_RUNTIME_DIR", "/run/user/1000");
|
||||
|
||||
assert_eq!(get_socket_dir(), PathBuf::from("/run/user/1000/agent-browser"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_socket_dir_ignores_empty_xdg_runtime() {
|
||||
let _guard = EnvGuard::new(&["AGENT_BROWSER_SOCKET_DIR", "XDG_RUNTIME_DIR"]);
|
||||
|
||||
env::set_var("AGENT_BROWSER_SOCKET_DIR", "");
|
||||
env::set_var("XDG_RUNTIME_DIR", "");
|
||||
|
||||
assert!(get_socket_dir().to_string_lossy().ends_with(".agent-browser"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_socket_dir_home_fallback() {
|
||||
let _guard = EnvGuard::new(&["AGENT_BROWSER_SOCKET_DIR", "XDG_RUNTIME_DIR"]);
|
||||
|
||||
env::remove_var("AGENT_BROWSER_SOCKET_DIR");
|
||||
env::remove_var("XDG_RUNTIME_DIR");
|
||||
|
||||
let result = get_socket_dir();
|
||||
assert!(result.to_string_lossy().ends_with(".agent-browser"));
|
||||
assert!(result.to_string_lossy().contains("home") || result.to_string_lossy().contains("Users"));
|
||||
}
|
||||
}
|
||||
|
||||
+7
-10
@@ -19,7 +19,7 @@ use windows_sys::Win32::Foundation::CloseHandle;
|
||||
use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION};
|
||||
|
||||
use commands::{gen_id, parse_command, ParseError};
|
||||
use connection::{ensure_daemon, send_command};
|
||||
use connection::{ensure_daemon, get_socket_dir, send_command};
|
||||
use flags::{clean_args, parse_flags};
|
||||
use install::run_install;
|
||||
use output::{print_command_help, print_help, print_response, print_version};
|
||||
@@ -59,21 +59,18 @@ fn run_session(args: &[String], session: &str, json_mode: bool) {
|
||||
|
||||
match subcommand {
|
||||
Some("list") => {
|
||||
let tmp = env::temp_dir();
|
||||
let socket_dir = get_socket_dir();
|
||||
let mut sessions: Vec<String> = Vec::new();
|
||||
|
||||
if let Ok(entries) = fs::read_dir(&tmp) {
|
||||
if let Ok(entries) = fs::read_dir(&socket_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
// Look for socket files (Unix) or pid files
|
||||
if name.starts_with("agent-browser-") && name.ends_with(".pid") {
|
||||
let session_name = name
|
||||
.strip_prefix("agent-browser-")
|
||||
.and_then(|s| s.strip_suffix(".pid"))
|
||||
.unwrap_or("");
|
||||
// Look for pid files in socket directory
|
||||
if name.ends_with(".pid") {
|
||||
let session_name = name.strip_suffix(".pid").unwrap_or("");
|
||||
if !session_name.is_empty() {
|
||||
// Check if session is actually running
|
||||
let pid_path = tmp.join(&name);
|
||||
let pid_path = socket_dir.join(&name);
|
||||
if let Ok(pid_str) = fs::read_to_string(&pid_path) {
|
||||
if let Ok(pid) = pid_str.trim().parse::<u32>() {
|
||||
#[cfg(unix)]
|
||||
|
||||
Reference in New Issue
Block a user