gitbutlerapp/gitbutler · error
Snapshot tree needs a 'HEAD' entry if it has a 'worktree'…
Error message
Snapshot tree {id} needs a 'HEAD' entry if it has a 'worktree' entry What it means
resolve_tree decodes a snapshot tree into a HEAD tree/index. A snapshot entry 'worktree' is only meaningful relative to a HEAD entry; if a snapshot tree contains 'worktree' but no 'HEAD' entry, resolution cannot proceed and it bails with the tree id.
Solutions
- Recreate the snapshot so it contains both 'HEAD' and 'worktree' entries
- Drop or rebuild the corrupted snapshot for this project so it is regenerated from current state
- Check the snapshot-writing code path to guarantee 'HEAD' is written whenever 'worktree' is
Defensive patterns
Strategy: validation
Validate before calling
let has = |k: &str| snapshot_tree.entries.iter().any(|e| e.filename == k);
if has("worktree") && !has("HEAD") { return Err(anyhow!("snapshot missing HEAD entry")); } Try / catch
match resolve_tree(repo, snapshot_tree) { Ok(t) => t, Err(_) => rebuild_snapshot(repo) } Prevention
- Never hand-build snapshot trees; use the writer API
- Regenerate snapshots after upgrading versions if the format changed
- Write 'HEAD' whenever 'worktree' is written
When it happens
Trigger: Resolving a snapshot tree whose entries contain 'worktree' but lack a 'HEAD' entry (invalid or hand-crafted snapshot, partially written snapshot, version mismatch between writer and reader).
Common situations: Corrupted or truncated snapshot data; snapshots written by an older/newer GitButler version with different entry conventions; manually constructed snapshot trees 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 only a…
- another pre-commit hook is already using the repository…
- askpass broker must be initialized
- Branch not found
- Branch ` ` is pushed, but its remote ancestry does not…
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/6530a5f855a8fd0a.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-core/src/snapshot/resolve_tree.rs:98
let worktree_cherry_pick = match (&head_tree, worktree) {
(Some(worktree_base), Some(worktree)) => {
let base = merge_base_override.unwrap_or_else(|| worktree_base.object_id());
let ours = target_worktree_tree_id;
let theirs = worktree.object_id();
repo.merge_trees(
base,
ours,
theirs,
repo.default_merge_labels(),
worktree_cherry_pick_options
.map(Ok)
.unwrap_or_else(|| repo.tree_merge_options())?,
)?
.into()
}
(None, Some(_worktree)) => {
bail!(
"Snapshot tree {id} needs a 'HEAD' entry if it has a 'worktree' entry",
id = snapshot_tree.id
)
}
(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)) => {View on GitHub (pinned to 58e5313667)