GitoxideLabs/gitoxide · error

the conflict checkout did not leave HEAD at

Error message

the conflict checkout did not leave HEAD at {commit}

What it means

After performing a conflict checkout, the code re-opens the repository and verifies HEAD points exactly at the expected conflict commit. `anyhow::ensure!(id == commit, ...)` fires when HEAD resolved to a different object id, meaning the checkout did not land where the operation expected.

Solutions

  1. Re-run the conflict checkout ensuring no other git process runs concurrently.
  2. Verify HEAD manually (`git rev-parse HEAD`) and reset it to the expected commit before resuming.
  3. Check for IDE/CI git integrations that may move HEAD during the operation.

Example fix

// before
anyhow::ensure!(id == commit, "the conflict checkout did not leave HEAD at {commit}");
// after
if id != commit {
    repository.set_head_commit_to(commit)?;
}
anyhow::ensure!(id == commit, "the conflict checkout did not leave HEAD at {commit}");
Defensive patterns

Strategy: validation

Validate before calling

let head_id = repo.head()?.id().map(|i| i.detach());
if head_id != Some(commit) {
    eprintln!("HEAD is not at the expected conflict commit; re-run the checkout");
    return;
}

Type guard

fn head_is_at(repo: &gix::Repository, commit: gix::ObjectId) -> bool {
    repo.head().ok().and_then(|h| h.id()).map(|i| i.detach()) == Some(commit)
}

Try / catch

let result = conflict_checkout(commit);
match result {
    Err(e) if e.to_string().contains("did not leave HEAD at") => retry_checkout(),
    other => other,
}

Prevention

When it happens

Trigger: A conflict checkout whose `head()` resolves to an id other than the target `commit` — e.g. another process moved HEAD between checkout and verification, or the checkout silently kept an existing HEAD position.

Common situations: Concurrent git operations (another terminal, IDE git integration) running while the tool checks out a conflict; a `git checkout` that failed partially; scripted automation touching HEAD mid-operation.

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


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/7ebcd035513669ac. Report an issue: GitHub.

Appendix: source

Thrown at gix-tix/src/lib.rs:4374

    repository_path: &Path,
    bare: bool,
    title: &str,
    changes: &mut Vec<edit::undo::RefChange>,
) -> Result<()> {
    let result = record_undo(repository_path, bare, title, changes);
    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 })
}

View on GitHub (pinned to e73179060b)