GitoxideLabs/gitoxide · error
a projected worktree reference contains a symbolic cycle
Error message
a projected worktree reference contains a symbolic cycle
What it means
`resolve_state` resolves the projected post-undo/redo state of a reference by following symbolic chains within the projected state. To guarantee termination it tracks visited full names; if the projected symbolic chain revisits a name, the projected state itself is cyclic — an unresolvable invariant violation — so this error is raised.
Solutions
- Inspect the queue commit's `[undo]` config metadata to find the changes forming the cycle.
- Break the cycle by pointing one of the refs at an object target before undoing (repair the live repo state so the projection is acyclic).
- Recreate the undo queue (discard and re-record) if its metadata is corrupt.
Example fix
// before: batch of changes that makes refs cyclic changes = [ref_a -> Symbolic(ref_b), ref_b -> Symbolic(ref_a)]; // after: at least one ref must resolve to an object changes = [ref_a -> Object(commit_id), ref_b -> Symbolic(ref_a)];
Defensive patterns
Strategy: validation
Validate before calling
// simulate the projected symref graph and detect cycles before applying changes
fn projected_cycle(changes: &[RefChange]) -> bool {
let mut seen = std::collections::HashSet::new();
for c in changes {
if let State::Symbolic(t) = &c.after {
if !seen.insert(t.clone()) { return true; }
}
}
false
} Prevention
- Ensure every symref chain in recorded changes terminates at an object target.
- Avoid batching changes that swap two symbolic refs.
- Treat queue metadata as immutable; regenerate rather than hand-edit.
When it happens
Trigger: Undo/redo (`apply_with_worktrees` -> `worktree_transitions` -> `resolve_state`, or nested `resolve_state` recursion) when the applied ref changes would make symbolic refs point at each other in a loop (e.g. change A to symref->B and B to symref->A).
Common situations: Undoing a batch that swapped two symbolic refs; hand-edited or corrupted queue metadata whose recorded changes describe cyclic symref states.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- a symbolic reference chain contains a cycle
- Could not follow all splits after
- the undo queue first-parent chain contains a cycle
- undo/redo cannot delete a worktree HEAD
- cannot delete an already-missing reference
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/55197cedf4f992c3.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/undo.rs:481
if name == b"HEAD" {
return Ok(fallback.clone());
}
let name = FullName::try_from(name).context("a projected worktree reference name is invalid")?;
state(repo, name.as_ref())
}
fn resolve_state(
repo: &gix::Repository,
state: &State,
changes: &[RefChange],
include_head: bool,
seen: &mut HashSet<FullName>,
) -> Result<Option<ObjectId>> {
match state {
State::Missing => Ok(None),
State::Object(id) => Ok(Some(*id)),
State::Symbolic(name) => {
ensure!(
seen.insert(name.clone()),
"a projected worktree reference contains a symbolic cycle"
);
let next = projected_ref_state(repo, name.as_bstr(), &State::Missing, changes, include_head)?;
resolve_state(repo, &next, changes, include_head, seen)
}
}
}
fn tree_id(repo: &gix::Repository, commit: Option<ObjectId>) -> Result<ObjectId> {
commit.map_or_else(
|| Ok(ObjectId::empty_tree(repo.object_hash())),
|commit| {
repo.find_commit(commit)
.context("a worktree HEAD target is not a commit")?
.tree_id()
.context("could not decode a worktree HEAD commit")
.map(gix::Id::detach)View on GitHub (pinned to e73179060b)