GitoxideLabs/gitoxide · error
a detached checked-out root commit cannot be removed
Error message
a detached checked-out root commit cannot be removed
What it means
When a rebase removes the commit currently checked out in a linked worktree, the worktree must be moved to some replacement tree. If the new commit is `None` (commit removed entirely) and the worktree HEAD is detached (no referent name), there is no branch to re-point and no commit to stay on, so removal of that detached checked-out root commit is refused.
Solutions
- Attach the worktree's HEAD to a branch (`git switch -c <branch>` or `git switch main`) so the referent-name fallback can move it to the empty tree.
- Attach the worktree to a surviving commit outside the rewritten range before running the removal.
- Don't remove that root commit — rewrite it instead so `new` is `Some(...)`.
- Remove the worktree entirely before the rebase edit.
Example fix
// before: worktree detached on the commit being removed // bail: "a detached checked-out root commit cannot be removed" // after: attach the worktree first run_in(worktree_path, &["git", "switch", "-c", "keep-alive"])?; edit.rebase_drop(root_id)?; // now succeeds
Defensive patterns
Strategy: validation
Validate before calling
if let Some(wt) = worktree_holding(repo, old_commit_id) {
let wt_repo = wt.into_repo()?;
let detached = wt_repo.head()?.referent_name().is_none();
if detached && removal_planned(old_commit_id) {
// attach the worktree to a branch before the edit
}
}
Prevention
- Never leave CI/scratch worktrees detached on commits inside rewrite ranges
- Attach worktrees to branches before dropping root commits
- Drop worktrees that are no longer needed before history rewrites
When it happens
Trigger: A rebase edit plans `new: None` for a commit `old` that a worktree has checked out; the worktree's `head()?.referent_name()` is `None` (detached HEAD), so neither `repo.find_commit(new)` nor `repo.empty_tree()` fallback applies and the code bails at `gix-tix/src/edit/rebase.rs:2929`.
Common situations: Rewriting history that drops the very root/first commit while a CI or scratch worktree sits detached on exactly that commit; force-cleanup scripts that drop root commits of experimental branches checked out detached in secondary worktrees.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- cannot delete because another worktree has it checked out
- Need a worktree to clean, this is a bare repository
- JSON output isn't implemented yet
- rebase todo requires at least one -x/--hide revision when…
- the hidden and visible revisions have no editable fork point
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/d86398349a198053.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/rebase.rs:2929
continue;
}
anyhow::bail!(
"cannot delete {} because another worktree has it checked out",
expected.name.shorten()
);
}
Some(expected) => Some(expected.new),
None => rewritten.get(&old).copied(),
};
let Some(new) = new else { continue };
if worktree_repo.workdir().is_none() && worktree_repo.is_bare() {
continue;
}
let old_tree = worktree_repo.find_commit(old)?.tree_id()?.detach();
let new_tree = match new {
Some(new) => repo.find_commit(new)?.tree_id()?.detach(),
None if worktree_repo.head()?.referent_name().is_some() => repo.empty_tree().id,
None => anyhow::bail!("a detached checked-out root commit cannot be removed"),
};
if old_tree == new_tree {
continue;
}
let workdir = worktree_repo
.workdir()
.filter(|path| path.is_dir())
.context("an affected worktree is inaccessible")?
.to_owned();
out.push(Transition {
repo: worktree_repo,
workdir,
old: old_tree,
new: new_tree,
});
}
Ok(out)
}View on GitHub (pinned to e73179060b)