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

  1. Filter paths before calling: strip ' -> ' rename markers, drop paths that no longer exist via `git -C <worktree_path> ls-files -- <path>`.
  2. Validate the worktree is still registered: `git -C <worktree_path> rev-parse --is-inside-worktree` before invoking diff.
  3. Upgrade git to a version that supports --find-renames (≥2.9) if you see 'unknown option`.
  4. 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

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


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/bc3ca3c1c9ce646a. Report an issue: GitHub.