GitoxideLabs/gitoxide · error
the conflicted HEAD attachment does not directly reference
Error message
the conflicted HEAD attachment does not directly reference {commit} What it means
After confirming HEAD's id matches the conflict commit, the code checks the undo-state of HEAD's referent: it must directly reference (State::Object) the conflict commit, not be unborn, symbolic to something else, or resolving indirectly. The ensure fires when `edit::undo::state` reports anything other than `State::Object(commit)`.
Solutions
- Re-attach HEAD to a reference that directly points at the conflict commit (e.g. `git checkout -B <branch> <commit>`).
- Clear/rebuild the stale undo state (`edit::undo::state`) so it reflects the actual HEAD referent.
- Repeat the conflict checkout so HEAD attachment and undo state are written together.
Example fix
// before
anyhow::ensure!(
edit::undo::state(&repository, name.as_ref())? == edit::undo::State::Object(commit),
"the conflicted HEAD attachment does not directly reference {commit}"
);
// after
if edit::undo::state(&repository, name.as_ref())? != edit::undo::State::Object(commit) {
repository.reference(name.as_bstr(), commit, gix::refs::transaction::PreviousValue::Any)?;
} Defensive patterns
Strategy: validation
Validate before calling
let state = edit::undo::state(&repo, referent.as_bstr())?;
assert!(matches!(state, edit::undo::State::Object(id) if id == commit),
"HEAD attachment must directly reference the conflict commit"); Type guard
fn directly_references(state: &edit::undo::State, commit: gix::ObjectId) -> bool {
matches!(state, edit::undo::State::Object(id) if *id == commit)
} Try / catch
match edit::undo::state(&repo, name.as_bstr()) {
Ok(s) if s != edit::undo::State::Object(commit) => rebuild_undo_state(),
Ok(_) => proceed(),
Err(e) => return Err(e.into()),
} Prevention
- Always create/update undo state in the same transaction as the checkout.
- Avoid manual `git checkout`/`git reset` on the same repo while undo state is live.
- Rebuild undo state after any crash or external mutation.
When it happens
Trigger: The HEAD referent's recorded undo state is not a direct object reference to `commit` — e.g. HEAD is unborn, attached to a branch pointing elsewhere, or the ref log/state was mutated after checkout.
Common situations: Undo bookkeeping interleaved with manual git commands that changed the branch HEAD points to; a detached-HEAD checkout where the expected attachment assumption breaks; stale undo state files.
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
- HEAD no longer directly references its replacement commit
- changes can only be stashed at the current HEAD
- changes can only be unstashed at the current HEAD
- undo/redo cannot delete a worktree HEAD
- the conflict checkout did not leave HEAD at
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/8d24f9c5ab10eb07.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/lib.rs:4380
changes.clear();
result
}
fn conflict_head(repository_path: &Path, bare: bool, commit: gix::ObjectId) -> Result<ConflictHead> {
let repository = open_repository(repository_path, bare, false)
.context("could not reopen the repository after checking out a conflict")?;
let head = repository.head().context("could not inspect the conflicted HEAD")?;
let id = head
.id()
.map(gix::Id::detach)
.context("the conflicted HEAD is unborn")?;
anyhow::ensure!(id == commit, "the conflict checkout did not leave HEAD at {commit}");
let reference = head.referent_name().map(ToOwned::to_owned);
drop(head);
let name = reference
.clone()
.unwrap_or_else(|| "HEAD".try_into().expect("valid reference name"));
anyhow::ensure!(
edit::undo::state(&repository, name.as_ref())? == edit::undo::State::Object(commit),
"the conflicted HEAD attachment does not directly reference {commit}"
);
let parents = repository
.find_commit(commit)
.context("could not find the checked-out conflict commit")?
.parent_ids()
.map(gix::Id::detach)
.collect();
Ok(ConflictHead { reference, parents })
}
fn reconcile_external_conflict(
repository_path: &Path,
bare: bool,
pending: &mut Option<PendingConflictResolution>,
) -> Result<ExternalConflictResolution> {
let state = pendingView on GitHub (pinned to e73179060b)