From b926441e880774ea6998badeb97d7e5153b52c50 Mon Sep 17 00:00:00 2001 From: Jack Felke Date: Sat, 7 Mar 2026 19:45:42 -0700 Subject: [PATCH] fix: broken shell syntax in git.run() calls (checkpoint, what-changed, session-health) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run() uses execFileSync which doesn't interpret shell operators (|, ||, &&, 2>/dev/null). These tools were passing shell pipelines/redirects as git args, causing silent failures or incorrect results. Fixed: - what-changed: use getDiffFiles() helper and array args for log - checkpoint: use array args for add/commit/reset instead of shell chains - session-health: use array args for diff --stat, split output in JS Same bug class as PR #170 (clarify-intent). Many more tools still affected — see forthcoming issue for tracking. --- src/tools/checkpoint.ts | 11 ++++++++--- src/tools/session-health.ts | 3 ++- src/tools/what-changed.ts | 7 ++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/tools/checkpoint.ts b/src/tools/checkpoint.ts index e086f01..d403395 100644 --- a/src/tools/checkpoint.ts +++ b/src/tools/checkpoint.ts @@ -84,11 +84,16 @@ ${dirty || "clean"} if (commitResult === "no uncommitted changes") { // Stage the checkpoint file too - run(`git add "${checkpointFile}"`); - const result = run(`${addCmd} && git commit -m "${commitMsg.replace(/"/g, '\\"')}" 2>&1`); + run(["add", checkpointFile]); + if (addCmd !== "true") { + // Run the staging command (e.g. "add -u" or "add -A") + const addArgs = addCmd.replace(/^git\s+/, "").split(/\s+/); + run(addArgs); + } + const result = run(["commit", "-m", commitMsg]); if (result.includes("commit failed") || result.includes("nothing to commit")) { // Rollback: unstage if commit failed - run("git reset HEAD 2>/dev/null"); + run(["reset", "HEAD"]); commitResult = `commit failed: ${result}`; } else { commitResult = result; diff --git a/src/tools/session-health.ts b/src/tools/session-health.ts index bd6a819..c3d859a 100644 --- a/src/tools/session-health.ts +++ b/src/tools/session-health.ts @@ -27,7 +27,8 @@ export function registerSessionHealth(server: McpServer): void { const dirtyCount = dirty ? dirty.split("\n").filter(Boolean).length : 0; const lastCommit = getLastCommit(); const lastCommitTimeStr = getLastCommitTime(); - const uncommittedDiff = run("git diff --stat | tail -1"); + const fullDiffStat = run(["diff", "--stat"]); + const uncommittedDiff = fullDiffStat.split("\n").filter(Boolean).pop() || ""; // Parse commit time safely const commitDate = parseGitDate(lastCommitTimeStr); diff --git a/src/tools/what-changed.ts b/src/tools/what-changed.ts index 913dfa2..2670422 100644 --- a/src/tools/what-changed.ts +++ b/src/tools/what-changed.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { run, getBranch, getDiffStat } from "../lib/git.js"; +import { run, getBranch, getDiffStat, getDiffFiles } from "../lib/git.js"; export function registerWhatChanged(server: McpServer): void { server.tool( @@ -12,8 +12,9 @@ export function registerWhatChanged(server: McpServer): void { async ({ since }) => { const ref = since || "HEAD~5"; const diffStat = getDiffStat(ref); - const diffFiles = run(`git diff ${ref} --name-only 2>/dev/null || git diff HEAD~3 --name-only`); - const log = run(`git log ${ref}..HEAD --oneline 2>/dev/null || git log -5 --oneline`); + const diffFiles = getDiffFiles(ref); + const logResult = run(["log", `${ref}..HEAD`, "--oneline"]); + const log = logResult.startsWith("[") ? run(["log", "-5", "--oneline"]) : logResult; const branch = getBranch(); const fileList = diffFiles.split("\n").filter(Boolean);