diff --git a/crates/app-tauri/src/mcp_endpoint.rs b/crates/app-tauri/src/mcp_endpoint.rs index b4dc728..3f9204b 100644 --- a/crates/app-tauri/src/mcp_endpoint.rs +++ b/crates/app-tauri/src/mcp_endpoint.rs @@ -87,13 +87,29 @@ impl McpEndpoint { /// per-user runtime dir (`$XDG_RUNTIME_DIR`, falling back to `$TMPDIR`, then /// `/tmp`). Kept short so the full socket path stays well under the ~108-byte /// `sockaddr_un` limit (`/run/user//idea-mcp/<32 hex>.sock` ≈ 50 bytes). +/// +/// The candidate bases are tried **in priority order**, returning the first whose +/// `idea-mcp` subdir exists or can be created. A set-but-unusable `$XDG_RUNTIME_DIR` +/// (e.g. a sandbox/CI where it points at a non-existent or read-only path) must not +/// dead-end the loopback bind: without this fallback the socket silently never +/// binds and inter-agent delegation dies. Deterministic for a given environment, so +/// the bind side and any `socket_path()` reader always agree on the same directory. #[cfg(unix)] fn unix_runtime_dir() -> PathBuf { - let base = std::env::var_os("XDG_RUNTIME_DIR") - .map(PathBuf::from) - .or_else(|| std::env::var_os("TMPDIR").map(PathBuf::from)) - .unwrap_or_else(|| PathBuf::from("/tmp")); - base.join("idea-mcp") + let candidates = [ + std::env::var_os("XDG_RUNTIME_DIR").map(PathBuf::from), + std::env::var_os("TMPDIR").map(PathBuf::from), + Some(PathBuf::from("/tmp")), + ]; + for base in candidates.into_iter().flatten() { + let dir = base.join("idea-mcp"); + if dir.is_dir() || std::fs::create_dir_all(&dir).is_ok() { + return dir; + } + } + // Last resort: keep the historical `/tmp` target even if creation just failed + // (the bind will surface the real error rather than silently picking nowhere). + PathBuf::from("/tmp").join("idea-mcp") } /// **Single source of truth.** Computes the deterministic loopback endpoint of a