From 0d1427f4e9ef46deaf2f81f82d84fecd088fc1e7 Mon Sep 17 00:00:00 2001 From: lanrtop Date: Mon, 6 Jul 2026 20:10:53 +0900 Subject: [PATCH] =?UTF-8?q?fix(mcp-inject):=20git-bash=20=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E5=A4=9A=E4=BD=8D=E7=BD=AE=E6=8E=A2=E6=B5=8B=E2=80=94?= =?UTF-8?q?=E2=80=94=E4=BF=AE=E5=A4=8D=20Scoop=20=E5=AE=89=E8=A3=85=20Git?= =?UTF-8?q?=20=E6=97=B6=20claude=20CLI=20=E8=B0=83=E7=94=A8=E5=85=A8?= =?UTF-8?q?=E6=8C=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 迁移实测发现:CLAUDE_CODE_GIT_BASH_PATH 回退路径硬编码 C:\Program Files\Git\bin\bash.exe,Scoop 安装的 Git 不在该位置, 导致 claude mcp add/remove 全部非零退出(存量 bug,组级时代同样存在)。 - find_git_bash():env(校验存在)→ Program Files / Scoop → where git.exe 推导 - try_register_globally 返回 bool;同名 already exists(exit 1)视为成功, 不再每次 inject 刷 warn 日志 - migrate_to_single_server 标记位改以注册成功为条件——CLI 链路不通时 不落标记,下次启动重试(幂等) --- src-tauri/src/mcp_inject.rs | 92 ++++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 16 deletions(-) diff --git a/src-tauri/src/mcp_inject.rs b/src-tauri/src/mcp_inject.rs index 25319de..d987c65 100644 --- a/src-tauri/src/mcp_inject.rs +++ b/src-tauri/src/mcp_inject.rs @@ -112,13 +112,47 @@ fn find_claude_bin() -> Option { None } -fn claude_git_bash() -> String { - std::env::var("CLAUDE_CODE_GIT_BASH_PATH") - .unwrap_or_else(|_| r"C:\Program Files\Git\bin\bash.exe".to_string()) +/// 查找 git-bash 路径(claude CLI 在 Windows 上必需)。 +/// 顺序:环境变量(校验存在)→ 常见安装位置(Program Files / Scoop)→ 从 git.exe 位置推导 +fn find_git_bash() -> Option { + if let Ok(p) = std::env::var("CLAUDE_CODE_GIT_BASH_PATH") { + if std::path::Path::new(&p).exists() { + return Some(p); + } + } + + let home = std::env::var("USERPROFILE").unwrap_or_default(); + let scoop = std::env::var("SCOOP").unwrap_or_else(|_| format!(r"{}\scoop", home)); + let candidates = [ + r"C:\Program Files\Git\bin\bash.exe".to_string(), + format!(r"{}\apps\git\current\bin\bash.exe", scoop), + ]; + for c in &candidates { + if std::path::Path::new(c).exists() { + return Some(c.clone()); + } + } + + // 从 git.exe 推导:\cmd\git.exe / \mingw64\bin\git.exe → \bin\bash.exe + if let Ok(out) = std::process::Command::new("where").arg("git.exe").output() { + if out.status.success() { + for line in String::from_utf8_lossy(&out.stdout).lines() { + let git = std::path::Path::new(line.trim()); + if let Some(root) = git.parent().and_then(|d| d.parent()) { + let bash = root.join("bin").join("bash.exe"); + if bash.exists() { + return Some(bash.to_string_lossy().to_string()); + } + } + } + } + } + None } -/// 将单一全局 lian-jing 服务器注册到 Claude 全局配置(~/.claude.json),best-effort,失败仅记录日志 -fn try_register_globally(port: u16) { +/// 将单一全局 lian-jing 服务器注册到 Claude 全局配置(~/.claude.json)。 +/// best-effort,失败仅记录日志;返回是否成功(同名已存在视为成功,作为 CLI 链路健康信号) +fn try_register_globally(port: u16) -> bool { let server_url = format!("http://127.0.0.1:{}/mcp", port); let Some(claude_bin) = find_claude_bin() else { @@ -128,10 +162,17 @@ fn try_register_globally(port: u16) { "claude CLI 未找到,跳过全局 MCP 注册", None, ); - return; + return false; + }; + let Some(git_bash) = find_git_bash() else { + db::log_event( + "mcp_inject", + "warn", + "git-bash 未找到(claude CLI 依赖),跳过全局 MCP 注册", + None, + ); + return false; }; - - let git_bash = claude_git_bash(); match std::process::Command::new(&claude_bin) .args([ @@ -149,13 +190,25 @@ fn try_register_globally(port: u16) { { Ok(out) if out.status.success() => { db::log_event("mcp_inject", "info", "全局 MCP 注册成功", Some(MCP_KEY)); + true } Ok(out) => { - let msg = format!( - "claude mcp add 非零退出: {}", + let combined = format!( + "{}{}", + String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr) ); - db::log_event("mcp_inject", "warn", &msg, Some(MCP_KEY)); + // 同名已存在(exit 1)不是故障——CLI 链路健康,注册目标已达成 + if combined.contains("already exists") { + return true; + } + db::log_event( + "mcp_inject", + "warn", + &format!("claude mcp add 非零退出: {}", combined), + Some(MCP_KEY), + ); + false } Err(e) => { db::log_event( @@ -164,6 +217,7 @@ fn try_register_globally(port: u16) { &format!("执行 claude mcp add 失败: {}", e), Some(MCP_KEY), ); + false } } } @@ -175,6 +229,10 @@ pub fn cleanup_legacy_global_registrations() -> Vec { db::log_event("mcp_inject", "warn", "claude CLI 未找到,跳过旧注册清理", None); return Vec::new(); }; + let Some(git_bash) = find_git_bash() else { + db::log_event("mcp_inject", "warn", "git-bash 未找到,跳过旧注册清理", None); + return Vec::new(); + }; let Ok(conn) = db::pool().get() else { return Vec::new(); }; @@ -186,7 +244,6 @@ pub fn cleanup_legacy_global_registrations() -> Vec { }) .unwrap_or_default(); - let git_bash = claude_git_bash(); let mut removed = Vec::new(); for gid in &group_ids { let mut names = vec![format!("lian-jing-{}", gid)]; @@ -316,22 +373,25 @@ pub fn migrate_to_single_server() { let errors = reinject_all_mcp(); let removed = cleanup_legacy_global_registrations(); + let register_ok = try_register_globally(crate::mcp_server::read_port()); let summary = format!( - "单一服务器迁移完成:重注入失败 {} 个,移除旧全局注册 {} 条{}", + "单一服务器迁移完成:重注入失败 {} 个,移除旧全局注册 {} 条,全局注册 {}{}", errors.len(), removed.len(), + if register_ok { "成功" } else { "失败" }, if errors.is_empty() { String::new() } else { format!(";失败详情: {}", errors.join("; ")) } ); - let level = if errors.is_empty() { "info" } else { "warn" }; + let level = if errors.is_empty() && register_ok { "info" } else { "warn" }; db::log_event("mcp_inject", level, &summary, None); - // claude CLI 不在时旧全局注册无法清理——不落标记位,等下次启动重试(整个流程幂等) - if find_claude_bin().is_none() { + // 全局注册失败说明 claude CLI 链路不通(CLI 缺失/git-bash 缺失/命令报错), + // 此时旧注册也清不掉——不落标记位,等下次启动重试(整个流程幂等) + if !register_ok { return; } if let Ok(conn) = db::pool().get() {