{"record":{"id":"55197cedf4f992c3","repo":"GitoxideLabs/gitoxide","slug":"a-projected-worktree-reference-contains-a-symbolic","errorCode":null,"errorMessage":"a projected worktree reference contains a symbolic cycle","messagePattern":"a projected worktree reference contains a symbolic cycle","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/undo.rs","lineNumber":481,"sourceCode":"    if name == b\"HEAD\" {\n        return Ok(fallback.clone());\n    }\n    let name = FullName::try_from(name).context(\"a projected worktree reference name is invalid\")?;\n    state(repo, name.as_ref())\n}\n\nfn resolve_state(\n    repo: &gix::Repository,\n    state: &State,\n    changes: &[RefChange],\n    include_head: bool,\n    seen: &mut HashSet<FullName>,\n) -> Result<Option<ObjectId>> {\n    match state {\n        State::Missing => Ok(None),\n        State::Object(id) => Ok(Some(*id)),\n        State::Symbolic(name) => {\n            ensure!(\n                seen.insert(name.clone()),\n                \"a projected worktree reference contains a symbolic cycle\"\n            );\n            let next = projected_ref_state(repo, name.as_bstr(), &State::Missing, changes, include_head)?;\n            resolve_state(repo, &next, changes, include_head, seen)\n        }\n    }\n}\n\nfn tree_id(repo: &gix::Repository, commit: Option<ObjectId>) -> Result<ObjectId> {\n    commit.map_or_else(\n        || Ok(ObjectId::empty_tree(repo.object_hash())),\n        |commit| {\n            repo.find_commit(commit)\n                .context(\"a worktree HEAD target is not a commit\")?\n                .tree_id()\n                .context(\"could not decode a worktree HEAD commit\")\n                .map(gix::Id::detach)","sourceCodeStart":463,"sourceCodeEnd":499,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/undo.rs#L463-L499","documentation":"`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.","triggerScenarios":"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).","commonSituations":"Undoing a batch that swapped two symbolic refs; hand-edited or corrupted queue metadata whose recorded changes describe cyclic symref states.","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."],"exampleFix":"// before: batch of changes that makes refs cyclic\nchanges = [ref_a -> Symbolic(ref_b), ref_b -> Symbolic(ref_a)];\n// after: at least one ref must resolve to an object\nchanges = [ref_a -> Object(commit_id), ref_b -> Symbolic(ref_a)];","handlingStrategy":"validation","validationCode":"// simulate the projected symref graph and detect cycles before applying changes\nfn projected_cycle(changes: &[RefChange]) -> bool {\n    let mut seen = std::collections::HashSet::new();\n    for c in changes {\n        if let State::Symbolic(t) = &c.after {\n            if !seen.insert(t.clone()) { return true; }\n        }\n    }\n    false\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["git","symbolic-ref","cycle","undo"],"backgroundTag":"internal-invariant-violation","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}