affaan-m/ECC · error
git diff failed: {stderr}
Error message
git diff failed: {stderr} What it means
Thrown by git_diff_patch_text_for_paths at ecc2/src/worktree/mod.rs:1079 when `git -C <worktree_path> diff --patch --find-renames <extra_args> -- <paths...>` exits non-zero. This helper is the engine behind filtered patch generation used to preview and stage specific files. Unlike the patch-lines sibling which downgrades failures to a warn + empty Vec, this hard variant bails because the caller needs the actual patch text.
Source
Thrown at ecc2/src/worktree/mod.rs:1079
let mut command = Command::new("git");
command
.arg("-C")
.arg(worktree_path)
.arg("diff")
.args(["--patch", "--find-renames"]);
command.args(extra_args);
command.arg("--");
for path in paths {
command.arg(path);
}
let output = command
.output()
.context("Failed to generate filtered git patch")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("git diff failed: {stderr}");
}
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
}
fn git_diff_patch_lines_for_paths(
worktree_path: &Path,
extra_args: &[&str],
paths: &[String],
) -> Result<Vec<String>> {
if paths.is_empty() {
return Ok(Vec::new());
}
let mut command = Command::new("git");
command
.arg("-C")
.arg(worktree_path)View on GitHub (pinned to 01e15490f0)
Solutions
- Filter paths before calling: strip ' -> ' rename markers, drop paths that no longer exist via `git -C <worktree_path> ls-files -- <path>`.
- Validate the worktree is still registered: `git -C <worktree_path> rev-parse --is-inside-worktree` before invoking diff.
- Upgrade git to a version that supports --find-renames (≥2.9) if you see 'unknown option`.
- Catch the error and degrade to a full-worktree diff without the path filter so the user still sees something.
Example fix
// before
let patch = git_diff_patch_text_for_paths(&wt.path, &[], &paths)?;
// after: validate paths against the worktree first
let known: HashSet<String> = list_worktree_files(&wt.path)?;
let valid: Vec<String> = paths.iter()
.filter(|p| known.contains(*p))
.cloned().collect();
if valid.is_empty() { return Ok(String::new()); }
let patch = git_diff_patch_text_for_paths(&wt.path, &[], &valid)?; Defensive patterns
Strategy: validation
Validate before calling
fn paths_known_to_worktree(worktree_path: &Path, paths: &[String]) -> Result<Vec<String>> {
let known: std::collections::HashSet<String> =
Command::new("git").arg("-C").arg(worktree_path)
.args(["ls-files", "--", "--"])
.output()?.stdout
.lines().map(|l| l.to_string()).collect();
Ok(paths.iter().filter(|p| known.contains(*p)).cloned().collect())
}
let valid = paths_known_to_worktree(&wt.path, &paths)?;
if valid.is_empty() { return Ok(String::new()); }
let patch = git_diff_patch_text_for_paths(&wt.path, &[], &valid)?; Type guard
fn is_valid_diff_path(worktree_path: &Path, p: &str) -> bool {
// quick reject obviously bad paths before calling git
!p.is_empty()
&& !p.starts_with('-')
&& !p.contains(" -> ")
&& std::path::Path::new(p).is_relative()
} Try / catch
match git_diff_patch_text_for_paths(&wt.path, &[], &paths) {
Ok(patch) => patch,
Err(e) if e.to_string().starts_with("git diff failed") => {
tracing::warn!("filtered diff failed, degrading to full diff: {e}");
git_diff_patch_text_for_paths(&wt.path, &[], &[])? // or full-worktree variant
}
Err(e) => return Err(e),
} Prevention
- Normalize paths from git_status_entries (strip ' -> ' rename markers) before passing to diff.
- Skip paths the user already deleted between status and diff.
- Pin a git version that supports --find-renames (>=2.9).
- Use git_diff_patch_lines_for_paths when you can tolerate empty fallback.
When it happens
Trigger: Passing a path that does not exist relative to the worktree root; passing a path with bad quoting or shell metacharacters; --cached/--staged extra_args combined with paths that have no staged changes on git versions that return non-zero; worktree path points to a directory that is no longer a valid git worktree (e.g. pruned); permission errors reading git objects.
Common situations: UI passes a display path (with ' -> ' rename marker) instead of the normalized path; path was deleted between the status snapshot and the diff call; worktree got pruned out from under the session; older git versions reject --find-renames synonyms; paths with non-UTF8 bytes on a system that mangles them.
Related errors
- Unable to infer ECC repo root from install-state operations
- git merge failed: {stderr}
- git rebase failed: {}{}
- git apply failed while trying to {action}: {stderr}
- git worktree list --porcelain failed: {stderr}
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/bc3ca3c1c9ce646a.
Report an issue: GitHub.