fix(mcp-inject): git-bash 路径多位置探测——修复 Scoop 安装 Git 时 claude CLI 调用全挂
All checks were successful
Push & PR Check / check (push) Successful in 1m32s

迁移实测发现: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 链路不通时
  不落标记,下次启动重试(幂等)
This commit is contained in:
lanrtop 2026-07-06 20:10:53 +09:00
parent 01eb941ab9
commit 0d1427f4e9

View File

@ -112,13 +112,47 @@ fn find_claude_bin() -> Option<String> {
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<String> {
if let Ok(p) = std::env::var("CLAUDE_CODE_GIT_BASH_PATH") {
if std::path::Path::new(&p).exists() {
return Some(p);
}
}
/// 将单一全局 lian-jing 服务器注册到 Claude 全局配置(~/.claude.jsonbest-effort失败仅记录日志
fn try_register_globally(port: u16) {
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 推导:<root>\cmd\git.exe / <root>\mingw64\bin\git.exe → <root>\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失败仅记录日志返回是否成功同名已存在视为成功作为 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<String> {
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<String> {
})
.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() {