affaan-m/ECC · error
git worktree list --porcelain failed: {stderr}
Error message
git worktree list --porcelain failed: {stderr} What it means
Thrown by base_checkout_path at ecc2/src/worktree/mod.rs:1581 when `git -C <worktree.path> worktree list --porcelain` exits non-zero. base_checkout_path parses that porcelain output to find which checkout owns refs/heads/<base_branch> (the merge/rebase target). It is called by merge_into_base, rebase_onto_base, and branch_head_oid, so this error can surface from any of them. Failure means git itself refused to enumerate worktrees — usually because the worktree path is no longer registered or git metadata is corrupt.
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 01e15490f0)
Solutions
- Check the worktree is still registered: `git -C <worktree_path> worktree list` — if missing, run `git -C <main_repo> worktree repair <worktree_path>` or recreate the worktree.
- If the worktree was deleted intentionally, prune and recreate: `git -C <main_repo> worktree prune && git -C <main_repo> worktree add <path> <branch>`.
- Verify the .git back-link inside the worktree is intact: `<worktree_path>/.git` should be a file pointing at `<main_repo>/.git/worktrees/<id>`.
- Upgrade git to ≥2.7 if porcelain output is unsupported.
Example fix
// before
let repo_root = base_checkout_path(&worktree)?;
// after: repair the worktree registration if porcelain fails
let repo_root = match base_checkout_path(&worktree) {
Ok(p) => p,
Err(e) if e.to_string().contains("worktree list --porcelain") => {
Command::new("git").arg("-C").arg(&worktree.path)
.args(["worktree", "repair"]).status()?;
base_checkout_path(&worktree)?
}
Err(e) => return Err(e),
}; Defensive patterns
Strategy: validation
Validate before calling
fn worktree_registered(worktree_path: &Path) -> Result<bool> {
let out = Command::new("git").arg("-C").arg(worktree_path)
.args(["rev-parse", "--is-inside-worktree"]).output()?;
Ok(out.status.success()
&& String::from_utf8_lossy(&out.stdout).trim() == "true")
}
if !worktree_registered(&worktree.path)? {
Command::new("git").arg("-C").arg(&worktree.path)
.args(["worktree", "repair"]).status()?;
}
let repo_root = base_checkout_path(&worktree)?; Type guard
fn gitfile_intact(worktree_path: &Path) -> bool {
let gitfile = worktree_path.join(".git");
gitfile.is_file() // linked worktrees use a .git file, not a directory
} Try / catch
match base_checkout_path(&worktree) {
Ok(p) => Ok(p),
Err(e) if e.to_string().starts_with("git worktree list --porcelain failed") => {
Command::new("git").arg("-C").arg(&worktree.path)
.args(["worktree", "repair"]).status()?;
base_checkout_path(&worktree)
}
Err(e) => Err(e),
} Prevention
- Never delete a worktree directory with rm -rf; use `git worktree remove`.
- Run `git worktree prune` on a schedule so dangling registrations are cleaned deterministically, not mid-call.
- Validate the .git back-link file at session start before trusting WorktreeInfo.path.
- Pin git >= 2.7 for porcelain output support.
When it happens
Trigger: The worktree was pruned (git worktree prune ran, or the user manually deleted it) leaving WorktreeInfo.path dangling; the .git file in the worktree points to a back-link that no longer exists; filesystem-level removal of <worktree_path>/.git; running inside a directory that looks like a worktree but is actually a standalone clone; git version too old to support `worktree list --porcelain`.
Common situations: Session restart after a reboot where another process pruned worktrees; dev manually rm -rf'd a worktree dir without `git worktree remove`; storage cleanup tools that scrub hidden .git files; very old git (<2.7) without porcelain format; the worktree lives on an unmounted or permission-restricted volume.
Related errors
- git rebase failed: {}{}
- git worktree add failed: {stderr}
- worktree_branch_prefix cannot be empty
- git status failed: {stderr}
- git add failed for {path}: {stderr}
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/57c81ff2ec5123b1.
Report an issue: GitHub.