{"record":{"id":"55bf122c50185634","repo":"GitoxideLabs/gitoxide","slug":"head-attachment-changed-while-resolving-the-confli","errorCode":null,"errorMessage":"HEAD attachment changed while resolving the conflict; return to the conflict checkout or exit","messagePattern":"HEAD attachment changed while resolving the conflict; return to the conflict checkout or exit","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/lib.rs","lineNumber":4408,"sourceCode":"    Ok(ConflictHead { reference, parents })\n}\n\nfn reconcile_external_conflict(\n    repository_path: &Path,\n    bare: bool,\n    pending: &mut Option<PendingConflictResolution>,\n) -> Result<ExternalConflictResolution> {\n    let state = pending\n        .as_ref()\n        .context(\"external conflict reconciliation requires pending state\")?;\n    let Some(expected) = state.head.as_ref() else {\n        return Ok(ExternalConflictResolution::Current);\n    };\n    let repository =\n        open_repository(repository_path, bare, false).context(\"could not inspect external conflict resolution\")?;\n    let head = repository.head().context(\"could not inspect HEAD after the conflict\")?;\n    let reference = head.referent_name().map(ToOwned::to_owned);\n    anyhow::ensure!(\n        reference == expected.reference,\n        \"HEAD attachment changed while resolving the conflict; return to the conflict checkout or exit\"\n    );\n    let replacement = head\n        .id()\n        .map(gix::Id::detach)\n        .context(\"HEAD became unborn while resolving the conflict\")?;\n    drop(head);\n    if replacement == state.commit {\n        return Ok(ExternalConflictResolution::Current);\n    }\n\n    let replacement_commit = repository\n        .find_commit(replacement)\n        .context(\"the replacement HEAD is not a commit\")?\n        .decode()\n        .context(\"could not decode the replacement HEAD commit\")?\n        .into_owned()","sourceCodeStart":4390,"sourceCodeEnd":4426,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/lib.rs#L4390-L4426","documentation":"When finalizing an external conflict resolution, the tool re-opens the repository and compares the current HEAD referent name with the one recorded in `expected.reference`. If the user (or another process) attached HEAD to a different branch while the conflict was being resolved, the ensure aborts and asks to return to the conflict checkout or exit.","triggerScenarios":"`head.referent_name()` returns a different branch than `expected.reference` — user ran `git checkout <other-branch>` or `git switch` while the external conflict resolution was in progress (e.g. an editor or merge tool was open).","commonSituations":"Users switching branches in another terminal or IDE while resolving conflicts in an external tool; scripts running `git switch` concurrently; resuming a session after unrelated git commands ran.","solutions":["Return to the conflict-checkout state (checkout the recorded reference `expected.reference`) before retrying the resolution.","Undo the branch switch: `git checkout <expected branch>` so HEAD attachment matches what was recorded.","Avoid running `git switch`/`git checkout` while an external conflict resolution is open."],"exampleFix":"// before\nanyhow::ensure!(\n    reference == expected.reference,\n    \"HEAD attachment changed while resolving the conflict; return to the conflict checkout or exit\"\n);\n// after\nif reference != expected.reference {\n    if let Some(name) = &expected.reference {\n        repository.checkout_reference(name)?;\n    }\n}\nanyhow::ensure!(\n    reference == expected.reference,\n    \"HEAD attachment changed while resolving the conflict; return to the conflict checkout or exit\"\n);","handlingStrategy":"validation","validationCode":"let current = repo.head()?.referent_name().map(|n| n.to_owned());\nif current != expected.reference {\n    eprintln!(\"switch back to {:?} before finalizing the conflict\", expected.reference);\n    return;\n}","typeGuard":"fn head_matches(repo: &gix::Repository, expected: &Option<gix::refs::FullName>) -> bool {\n    repo.head().ok().and_then(|h| h.referent_name().map(|n| n.to_owned())) == *expected\n}","tryCatchPattern":"if let Err(e) = finalize_conflict(&expected) {\n    if e.to_string().contains(\"HEAD attachment changed\") {\n        prompt_return_to_conflict_checkout();\n    } else { return Err(e.into()); }\n}","preventionTips":["Don't run `git switch`/`git checkout` in other terminals while an external conflict resolution is open.","Pause IDE auto-fetch/checkout integrations during conflict resolution.","Record and re-verify HEAD referent at each finalization step."],"tags":["git","head","branch-switch","conflict"],"backgroundTag":"invalid-state-transition","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}