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

git worktree list --porcelain failed

Error message

git worktree list --porcelain failed: {stderr}

What it means

Raised when `git worktree list --porcelain` exits non-zero while the library is resolving the checkout path for a worktree's base branch. Since this command is the canonical source of worktree metadata, failure means git could not enumerate worktrees — corrupt .git/worktrees metadata, not a repo, or a git version too old to support --porcelain for worktree list.

Solutions

  1. Confirm the process cwd (repo root) is inside a git repository: 'git rev-parse --is-inside-work-tree'
  2. Run 'git worktree prune' to clean stale worktree admin metadata
  3. Check 'git --version' — upgrade git to >= 2.7 where --porcelain is supported for worktree list
  4. Read the stderr in the message for git's specific complaint (e.g. 'not a git repository')
  5. If a worktree dir was deleted manually, 'git worktree repair' or recreate it

Example fix

// before: assumes valid repo
let path = base_checkout_path(&wt)?;
// after: pre-check repo validity
let ok = Command::new("git")
    .args(["rev-parse", "--is-inside-work-tree"])
    .current_dir(&wt.path)
    .output()?;
anyhow::ensure!(ok.status.success(), "{} is not a git repository", wt.path.display());
let path = base_checkout_path(&wt)?;
Defensive patterns

Strategy: try-catch

Validate before calling

let is_repo = Command::new("git")
    .args(["rev-parse", "--is-inside-work-tree"])
    .current_dir(&wt.path)
    .output()
    .map(|o| o.status.success())
    .unwrap_or(false);
anyhow::ensure!(is_repo, "{} is not a git repository", wt.path.display());

Try / catch

match base_checkout_path(&wt) {
    Ok(p) => p,
    Err(e) if e.to_string().contains("not a git repository") => {
        eprintln!("worktree {} is outside a repo or has stale metadata", wt.path.display());
        // 'git worktree prune' or 'git worktree repair' before retry
        Err(e)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Calling worktree resolution outside a git repository (or with a corrupted GIT_DIR), with git < 2.7 lacking 'worktree list --porcelain', or with a damaged .git/worktrees directory containing stale admin files.

Common situations: Running the tool in a subdirectory that is not a repo (wrong cwd); pruned worktree metadata left behind after manual directory deletion; ancient system git on a CI container.

Understand the failure class

Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/57c81ff2ec5123b1. Report an issue: GitHub.

Appendix: source

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

        .arg(repo_root)
        .args(["rev-parse", "--abbrev-ref", "HEAD"])
        .output()
        .context("Failed to get current branch")?;

    Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}

fn base_checkout_path(worktree: &WorktreeInfo) -> Result<PathBuf> {
    let output = Command::new("git")
        .arg("-C")
        .arg(&worktree.path)
        .args(["worktree", "list", "--porcelain"])
        .output()
        .context("Failed to resolve git worktree list")?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        anyhow::bail!("git worktree list --porcelain failed: {stderr}");
    }

    let target_branch = format!("refs/heads/{}", worktree.base_branch);
    let mut current_path: Option<PathBuf> = None;
    let mut current_branch: Option<String> = None;
    let mut fallback: Option<PathBuf> = None;

    for line in String::from_utf8_lossy(&output.stdout).lines() {
        if line.is_empty() {
            if let Some(path) = current_path.take() {
                if fallback.is_none() && path != worktree.path {
                    fallback = Some(path.clone());
                }
                if current_branch.as_deref() == Some(target_branch.as_str())
                    && path != worktree.path
                {
                    return Ok(path);
                }

View on GitHub (pinned to 8321021c54)