{"record":{"id":"bc3ca3c1c9ce646a","repo":"affaan-m/ECC","slug":"git-diff-failed-stderr","errorCode":null,"errorMessage":"git diff failed: {stderr}","messagePattern":"git diff failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ecc2/src/worktree/mod.rs","lineNumber":1079,"sourceCode":"    let mut command = Command::new(\"git\");\n    command\n        .arg(\"-C\")\n        .arg(worktree_path)\n        .arg(\"diff\")\n        .args([\"--patch\", \"--find-renames\"]);\n    command.args(extra_args);\n    command.arg(\"--\");\n    for path in paths {\n        command.arg(path);\n    }\n\n    let output = command\n        .output()\n        .context(\"Failed to generate filtered git patch\")?;\n\n    if !output.status.success() {\n        let stderr = String::from_utf8_lossy(&output.stderr);\n        anyhow::bail!(\"git diff failed: {stderr}\");\n    }\n\n    Ok(String::from_utf8_lossy(&output.stdout).into_owned())\n}\n\nfn git_diff_patch_lines_for_paths(\n    worktree_path: &Path,\n    extra_args: &[&str],\n    paths: &[String],\n) -> Result<Vec<String>> {\n    if paths.is_empty() {\n        return Ok(Vec::new());\n    }\n\n    let mut command = Command::new(\"git\");\n    command\n        .arg(\"-C\")\n        .arg(worktree_path)","sourceCodeStart":1061,"sourceCodeEnd":1097,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/worktree/mod.rs#L1061-L1097","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet patch = git_diff_patch_text_for_paths(&wt.path, &[], &paths)?;\n\n// after: validate paths against the worktree first\nlet known: HashSet<String> = list_worktree_files(&wt.path)?;\nlet valid: Vec<String> = paths.iter()\n    .filter(|p| known.contains(*p))\n    .cloned().collect();\nif valid.is_empty() { return Ok(String::new()); }\nlet patch = git_diff_patch_text_for_paths(&wt.path, &[], &valid)?;","handlingStrategy":"validation","validationCode":"fn paths_known_to_worktree(worktree_path: &Path, paths: &[String]) -> Result<Vec<String>> {\n    let known: std::collections::HashSet<String> =\n        Command::new(\"git\").arg(\"-C\").arg(worktree_path)\n            .args([\"ls-files\", \"--\", \"--\"])\n            .output()?.stdout\n            .lines().map(|l| l.to_string()).collect();\n    Ok(paths.iter().filter(|p| known.contains(*p)).cloned().collect())\n}\nlet valid = paths_known_to_worktree(&wt.path, &paths)?;\nif valid.is_empty() { return Ok(String::new()); }\nlet patch = git_diff_patch_text_for_paths(&wt.path, &[], &valid)?;","typeGuard":"fn is_valid_diff_path(worktree_path: &Path, p: &str) -> bool {\n    // quick reject obviously bad paths before calling git\n    !p.is_empty()\n        && !p.starts_with('-')\n        && !p.contains(\" -> \")\n        && std::path::Path::new(p).is_relative()\n}","tryCatchPattern":"match git_diff_patch_text_for_paths(&wt.path, &[], &paths) {\n    Ok(patch) => patch,\n    Err(e) if e.to_string().starts_with(\"git diff failed\") => {\n        tracing::warn!(\"filtered diff failed, degrading to full diff: {e}\");\n        git_diff_patch_text_for_paths(&wt.path, &[], &[])? // or full-worktree variant\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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."],"tags":["git","diff","path-validation","subprocess-failure"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}