affaan-m/ECC · error · anyhow::Error

git add failed for {path}: {stderr}

Error message

git add failed for {path}: {stderr}

What it means

stage_path runs `git -C <worktree.path> add -- <path>` and re-throws git's stderr on a non-zero exit. The `--` separator means the path is treated literally (no option injection), so failures are genuine git refusals, not parsing issues. The `with_context` wrapper covers the spawn-failure case (git binary missing); the bail! covers the ran-but-failed case.

Source

Thrown at ecc2/src/worktree/mod.rs:304

    Ok(String::from_utf8_lossy(&output.stdout)
        .lines()
        .filter_map(parse_git_status_entry)
        .collect())
}

pub fn stage_path(worktree: &WorktreeInfo, path: &str) -> Result<()> {
    let output = Command::new("git")
        .arg("-C")
        .arg(&worktree.path)
        .args(["add", "--"])
        .arg(path)
        .output()
        .with_context(|| format!("Failed to stage {}", path))?;
    if output.status.success() {
        Ok(())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr);
        anyhow::bail!("git add failed for {path}: {stderr}");
    }
}

pub fn unstage_path(worktree: &WorktreeInfo, path: &str) -> Result<()> {
    let output = Command::new("git")
        .arg("-C")
        .arg(&worktree.path)
        .args(["reset", "HEAD", "--"])
        .arg(path)
        .output()
        .with_context(|| format!("Failed to unstage {}", path))?;
    if output.status.success() {
        Ok(())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr);
        anyhow::bail!("git reset failed for {path}: {stderr}");
    }
}

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Re-fetch the status entry list right before staging and confirm the path still appears.
  2. If the file is gitignored and you genuinely want it staged, switch to `git add --force -- <path>` (requires a code change to stage_path).
  3. Remove `.git/index.lock` if a previous git process was killed.
  4. Ensure the path is relative to `worktree.path` (not to the process cwd) when invoking stage_path.

Example fix

// before
stage_path(&worktree, path)?;

// after
let target = worktree.path.join(path);
if !target.exists() {
    anyhow::bail!("cannot stage, path no longer exists: {}", target.display());
}
stage_path(&worktree, path)?;
Defensive patterns

Strategy: validation

Validate before calling

fn stageable(worktree: &WorktreeInfo, path: &str) -> bool {
    worktree.path.join(path).exists()
}

if !stageable(&worktree, path) {
    return Err(anyhow!("path does not exist; refresh status"));
}
stage_path(&worktree, path)?;

Try / catch

match stage_path(&worktree, path) {
    Ok(()) => { /* refresh status */ }
    Err(e) => {
        let msg = format!("{e:#}");
        if msg.contains("index.lock") {
            // surface 'another git operation in progress'
        } else if msg.contains("ignored") {
            // ask user whether to force-add
        } else {
            return Err(e);
        }
    }
}

Prevention

When it happens

Trigger: Staging a path that does not exist in the worktree, is matched by `.gitignore`/`.git/info/exclude` (without `-f`), is outside the repository boundary, or when a concurrent git process holds `.git/index.lock`. Also when the path is a gitlink/submodule pointer git refuses to add directly.

Common situations: UI passes a relative path computed against the wrong cwd; path was deleted between status refresh and stage action; `.gitignore` rule added after the file was tracked-then-removed; ESLint/prettier hook rewrote and removed the file mid-staging; submodule path passed where git expects a regular entry.

Related errors


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