{"record":{"id":"0943ecc9da45054a","repo":"GitoxideLabs/gitoxide","slug":"a-symbolic-reference-chain-contains-a-cycle","errorCode":null,"errorMessage":"a symbolic reference chain contains a cycle","messagePattern":"a symbolic reference chain contains a cycle","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/undo.rs","lineNumber":123,"sourceCode":"                anyhow::Error::new(err).context(\"could not atomically move references and the undo cursor\"),\n            ));\n        }\n        Ok(())\n    }\n}\n\npub(crate) fn is_queue_ref(name: &BStr) -> bool {\n    name.as_bytes() == TIP_REF.as_bytes() || name.as_bytes() == CURSOR_REF.as_bytes()\n}\n\npub(crate) fn ref_chain_reaches_queue(repo: &gix::Repository, name: &FullNameRef) -> Result<bool> {\n    let mut name = name.to_owned();\n    let mut seen = HashSet::new();\n    loop {\n        if is_queue_ref(name.as_bstr()) {\n            return Ok(true);\n        }\n        ensure!(seen.insert(name.clone()), \"a symbolic reference chain contains a cycle\");\n        let Some(reference) = repo.try_find_reference(name.as_ref())? else {\n            return Ok(false);\n        };\n        let target = reference.target();\n        let Some(next) = target.try_name() else {\n            return Ok(false);\n        };\n        name = next.to_owned();\n    }\n}\n\npub(crate) fn is_queue_commit(repo: &gix::Repository, needle: ObjectId) -> Result<bool> {\n    let Some(mut id) = read_queue_ref(repo, TIP_REF)?.or(read_queue_ref(repo, CURSOR_REF)?) else {\n        return Ok(false);\n    };\n    let mut seen = HashSet::new();\n    loop {\n        ensure!(seen.insert(id), \"the undo queue first-parent chain contains a cycle\");","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/undo.rs#L105-L141","documentation":"`ref_chain_reaches_queue` walks a symbolic reference chain (ref -> symref target -> next target) looking for an undo-queue ref. To guarantee termination it records each visited name in a `HashSet`; re-visiting a name means the symref chain is cyclic, which is never valid, so it raises this error instead of looping forever.","triggerScenarios":"Any lookup that walks symbolic refs from a name while the repository contains a symref cycle, e.g. ref A -> ref B -> ref A. Triggered from `ref_chain_reaches_queue` during undo planning.","commonSituations":"Hand-edited or corrupted refs directory; a tool or script created `refs/foo` as a symref of `refs/bar` and vice versa; repository tampering or interrupted ref updates.","solutions":["Find the cycle with `git for-each-ref --format='%(refname) %(symref)'` and inspect the offending symrefs.","Break the cycle by repointing or deleting one of the symbolic refs (e.g. `git symbolic-ref refs/foo refs/main` or `git update-ref -d refs/foo`).","Never create symref cycles programmatically; validate targets before `create_reference(..., Target::Symbolic(...))`."],"exampleFix":"// before: creating a cycle\nrepo.reference(\"refs/a\").create(gix::refs::Target::Symbolic(\"refs/b\".into()), ...);\nrepo.reference(\"refs/b\").create(gix::refs::Target::Symbolic(\"refs/a\".into()), ...);\n// after: point the symref at a real (non-symref) target\nrepo.reference(\"refs/b\").create(gix::refs::Target::Object(commit_id), ...);","handlingStrategy":"validation","validationCode":"// verify the symref chain from a name terminates before operations\nfn symref_chain_is_acyclic(repo: &gix::Repository, name: &str) -> anyhow::Result<bool> {\n    let mut seen = std::collections::HashSet::new();\n    let mut cur = name.to_owned();\n    while let Some(r) = repo.try_find_reference(&cur)? {\n        let Some(next) = r.target().try_name() else { return Ok(true) };\n        if !seen.insert(next.to_owned()) { return Ok(false); }\n        cur = next.to_string();\n    }\n    Ok(true)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never create a symbolic ref whose target chain can loop back to itself.","Audit hand-written symrefs with `git for-each-ref --format='%(refname) %(symref)'`.","Use library APIs (which validate targets) instead of raw file edits for symrefs."],"tags":["git","symbolic-ref","cycle","repository-corruption"],"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"}