gitbutlerapp/gitbutler · error · anyhow::Error
Refusing to check out conflicted commit {new_head_id}
Error message
Refusing to check out conflicted commit {new_head_id} What it means
The worktree checkout function peels `new_head_id` to an object and, unless `Options::allow_conflicted_commit_checkout` is set, refuses when the target commit is a GitButler conflicted (conflict-crash) commit — checking such a commit out would materialize conflict-marker trees as if they were real content. This is a deliberate safety guard with an explicit opt-in flag.
Source
Thrown at crates/but-core/src/worktree/checkout/function.rs:47
/// If this changes, then the source sid of a rename could also cause conflicts, maybe? It's a bit unclear what it would mean
/// in practice, but I guess that we bring deleted files back instead of conflicting.
#[instrument(skip(repo), err(Debug))]
pub fn safe_checkout_from_head(
new_head_id: gix::ObjectId,
repo: &gix::Repository,
Options {
skip_head_update,
merge_base_override,
allow_conflicted_commit_checkout,
allow_uncommitted_changes_to_conflict_with_new_head,
}: Options,
) -> anyhow::Result<Outcome> {
let new_object = new_head_id.attach(repo).object()?;
if !allow_conflicted_commit_checkout
&& new_object.kind.is_commit()
&& crate::Commit::from_id(new_head_id.attach(repo))?.is_conflicted()
{
bail!("Refusing to check out conflicted commit {new_head_id}");
}
let git2_repo = git2::Repository::open(repo.git_dir())?;
let head_tree_id = repo.head_tree_id_or_empty()?;
let head_tree = git2_repo.find_tree(head_tree_id.to_git2())?;
let old_tree = if let Some(id) = merge_base_override {
let mut opts = git2::DiffOptions::new();
opts.context_lines(1);
// Also write the index.
let tree = git2_repo.find_object(id.to_git2(), None)?.peel_to_tree()?;
let diff = git2_repo.diff_tree_to_tree(Some(&head_tree), Some(&tree), Some(&mut opts))?;
if git2_repo
.apply(&diff, git2::ApplyLocation::Index, None)
.is_err()
{
// Just overwrite the index.
git2_repo.index()?.read_tree(&tree)?;
}View on GitHub (pinned to caf1f223d3)
Solutions
- Resolve or abandon the conflict first, then check out the resolved commit.
- If you genuinely intend to inspect a conflicted state, pass `allow_conflicted_commit_checkout: true` in the checkout `Options` and handle the conflict trees knowingly.
- Verify the commit beforehand: `but_core::Commit::from_id(...)?.is_conflicted()`.
Example fix
// before
let out = but_core::worktree::checkout(repo, target, Options::default())?;
// after — explicitly allow conflicted (conflict-crash) commits
let out = but_core::worktree::checkout(
repo,
target,
Options { allow_conflicted_commit_checkout: true, ..Default::default() },
)?; Defensive patterns
Strategy: validation
Validate before calling
// Rust — check the target before checkout
let commit = but_core::Commit::from_id(new_head_id.attach(repo))?;
if commit.is_conflicted() && !opts.allow_conflicted_commit_checkout {
// refuse early with an actionable message / offer conflict resolution
return Err(anyhow!("target {new_head_id} is a conflicted commit; resolve conflicts first"));
}
let outcome = checkout(repo, new_head_id, opts)?; Type guard
fn is_safe_checkout_target(commit: &but_core::Commit<'_>, opts: &Options) -> bool {
opts.allow_conflicted_commit_checkout || !commit.is_conflicted()
} Try / catch
match checkout(repo, target, opts) {
Ok(outcome) => outcome,
Err(err) if err.to_string().contains("Refusing to check out conflicted commit") => {
// offer resolve-conflicts flow, or re-run with allow flag if inspection was intended
resolve_conflicts_first(repo, target).await?;
checkout(repo, target, opts)
}
Err(err) => Err(err),
} Prevention
- Filter conflicted commits out of any programmatically-built list of checkout targets.
- Only set `allow_conflicted_commit_checkout` in read-only/inspection tooling that understands conflict trees.
- Resolve or undo conflict states before navigating history in a workspace.
When it happens
Trigger: Calling the checkout API with a commit id that `Commit::is_conflicted()` returns true for (message/tree carry conflict metadata), without setting `allow_conflicted_commit_checkout: true` in `Options`.
Common situations: Programmatic navigation to a historical conflict state; snapshots/undo trying to restore a conflicted commit; debug tooling checking out arbitrary commit ids.
Related errors
- Invalid token format
- Login token expired. Please log in to GitButler again.
- HTTP Error ${response.statusText}: ${text}
- worktree-changes are always set if there are hunks
- Worktree {name} has no usable HEAD
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/7141062691833bea.
Report an issue: GitHub.