gitbutlerapp/gitbutler · error
Snapshot tree needs a 'HEAD' entry if it has only a…
Error message
Snapshot tree {id} needs a 'HEAD' entry if it has only a 'index-conflicts' entry What it means
resolve_tree requires a 'HEAD' entry as the base whenever 'index-conflicts' is present: conflicts are restored by replacing entries in an index built from the worktree/HEAD base. A snapshot with only 'index-conflicts' (no HEAD, no worktree) has no base index, so it bails.
Solutions
- Rebuild or delete the malformed snapshot so it is regenerated with a proper 'HEAD' base
- Include a 'HEAD' entry in the snapshot whenever 'index-conflicts' is written
- Fix the snapshot writer to enforce the HEAD+index-conflicts invariant
Defensive patterns
Strategy: validation
Validate before calling
let has = |k: &str| snapshot_tree.entries.iter().any(|e| e.filename == k);
if has("index-conflicts") && !has("HEAD") && !has("worktree") { return Err(anyhow!("snapshot has index-conflicts without base")); } Try / catch
match resolve_tree(repo, snapshot_tree) { Ok(t) => t, Err(_) => { clear_snapshot(repo); resolve_tree(repo, fresh_snapshot(repo)?)? } } Prevention
- Write a 'HEAD' base entry whenever 'index-conflicts' is recorded
- Rebuild malformed snapshots instead of resolving them
- Add writer-side invariant tests for snapshot entry combinations
When it happens
Trigger: Resolving a snapshot tree containing an 'index-conflicts' entry but neither a 'HEAD' nor 'worktree' entry, so there is no index to restore conflicts into.
Common situations: Corrupted or partially written snapshots; snapshots edited or produced by a mismatched writer version; synthetic snapshot trees built incorrectly in tests.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Snapshot tree needs a 'HEAD' entry if it has a 'worktree'…
- Invalid stage ' ' for
- stale pre-commit index backup at
- Unconflicted stage for
- another pre-commit hook is already using the repository…
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/7ba6455d9fe61ed6.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-core/src/snapshot/resolve_tree.rs:121
(None, None) | (Some(_), None) => None,
};
let index = match (&head_tree, index, index_conflicts) {
(_, Some(index_tree), Some(index_conflicts)) => {
let (mut index, _path) = repo.index_from_tree(&index_tree.id())?.into_parts();
replace_entries_with_their_restored_conflicts(&mut index, index_conflicts.id())?;
Some(index)
}
(_, Some(index_tree), None) => {
let (index, _path) = repo.index_from_tree(&index_tree.id())?.into_parts();
Some(index)
}
(Some(worktree_base), None, Some(index_conflicts)) => {
let (mut index, _path) = repo.index_from_tree(&worktree_base.id())?.into_parts();
replace_entries_with_their_restored_conflicts(&mut index, index_conflicts.id())?;
Some(index)
}
(None, None, Some(_index_conflicts)) => bail!(
"Snapshot tree {id} needs a 'HEAD' entry if it has only a 'index-conflicts' entry",
id = snapshot_tree.id
),
(_, None, None) => None,
};
Ok(Outcome {
worktree_cherry_pick,
index,
workspace_references: None,
metadata: None,
})
}
#[expect(clippy::indexing_slicing)]
fn replace_entries_with_their_restored_conflicts(
index: &mut gix::index::State,
conflict_tree: gix::Id,View on GitHub (pinned to 58e5313667)