fix(mcp): fallback déterministe du runtime dir socket

`unix_runtime_dir` essaie désormais les bases candidates dans l'ordre de
priorité ($XDG_RUNTIME_DIR → $TMPDIR → /tmp) et retient la première dont
le sous-dossier `idea-mcp` existe ou peut être créé. Un
$XDG_RUNTIME_DIR positionné mais inutilisable (sandbox/CI pointant un
chemin inexistant ou en lecture seule) ne doit plus dead-end le bind
loopback : sans ce fallback le socket ne se liait jamais en silence et la
délégation inter-agents mourait. Déterministe pour un environnement donné,
donc le côté bind et tout lecteur de `socket_path()` s'accordent sur le
même répertoire.

Indépendant du Lot E1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 02:00:13 +02:00
parent b12081be18
commit 8c7c47c0e8

View File

@ -87,13 +87,29 @@ impl McpEndpoint {
/// per-user runtime dir (`$XDG_RUNTIME_DIR`, falling back to `$TMPDIR`, then /// 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 /// `/tmp`). Kept short so the full socket path stays well under the ~108-byte
/// `sockaddr_un` limit (`/run/user/<uid>/idea-mcp/<32 hex>.sock` ≈ 50 bytes). /// `sockaddr_un` limit (`/run/user/<uid>/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)] #[cfg(unix)]
fn unix_runtime_dir() -> PathBuf { fn unix_runtime_dir() -> PathBuf {
let base = std::env::var_os("XDG_RUNTIME_DIR") let candidates = [
.map(PathBuf::from) std::env::var_os("XDG_RUNTIME_DIR").map(PathBuf::from),
.or_else(|| std::env::var_os("TMPDIR").map(PathBuf::from)) std::env::var_os("TMPDIR").map(PathBuf::from),
.unwrap_or_else(|| PathBuf::from("/tmp")); Some(PathBuf::from("/tmp")),
base.join("idea-mcp") ];
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 /// **Single source of truth.** Computes the deterministic loopback endpoint of a