gitbutlerapp/gitbutler · critical
snapshot checkout and workspace commits disagree
Error message
snapshot checkout and workspace commits disagree
What it means
For managed snapshots, the checkout ref is the GitButler workspace ref, and the recorded checkout commit must equal the commit recorded under the snapshot's workspace branch entry. This bail fires when the two records disagree — an internally inconsistent snapshot that cannot be restored safely.
Source
Thrown at crates/gitbutler-oplog/src/oplog.rs:1026
}
// TODO: in the next iteration, this of course can't be hardcoded.
if branch_name == "workspace" {
restored_workspace_commit = Some(commit_oid);
}
}
}
if let Some(checkout) = restored_checkout.as_ref() {
if !repo.has_object(checkout.commit_id) {
bail!(
"snapshot checkout commit {} is unavailable",
checkout.commit_id
);
}
if checkout.ref_name.as_ref() == workspace_ref
&& restored_workspace_commit != Some(checkout.commit_id)
{
bail!("snapshot checkout and workspace commits disagree");
}
}
// Managed snapshots already identify their checkout through the workspace commit entry.
let restored_checkout = restored_checkout.or_else(|| {
restored_workspace_commit.map(|commit_id| SnapshotCheckout {
ref_name: workspace_ref.to_owned(),
commit_id,
})
});
let head = repo.head()?;
let head_ref = head
.referent_name()
.context("We will not change a worktree in detached HEAD state")?;
// Snapshots with neither checkout identity nor a workspace commit retain the old guard.
if restored_checkout.is_none() && head_ref != workspace_ref {
bail!("cannot restore a snapshot without checkout identity outside the workspace branch");
}View on GitHub (pinned to caf1f223d3)
Solutions
- Restore the next-older oplog entry — one entry back is usually consistent
- Report with the snapshot id; managed snapshots should be written atomically, so disagreement indicates a bug
- As a last resort, manually check out the desired workspace commit and let GitButler create a fresh snapshot
Defensive patterns
Strategy: fallback
Validate before calling
if checkout.ref_name.as_ref() == workspace_ref
&& restored_workspace_commit != Some(checkout.commit_id)
{
return Err(anyhow::anyhow!("snapshot is internally inconsistent; skip it"));
} Try / catch
match oplog.restore(snapshot_id) {
Err(err) if err.to_string().contains("commits disagree") => { /* step one entry back in the oplog */ }
other => other,
} Prevention
- Let the app exit cleanly so oplog snapshot writes finish atomically
- Validate snapshot consistency in restore-path tests
When it happens
Trigger: Restoring a snapshot where `checkout/ref` is the workspace ref but `checkout/commit` differs from the workspace branch's last recorded commit — mixed writes, partial snapshot creation, or a corrupted oplog tree.
Common situations: App killed mid-snapshot-write; oplog trees edited or restored piecemeal; version migrations assembling snapshots incorrectly.
Related errors
- targetRef in project_meta.toml is not a remote-tracking bran
- targetCommitId in project_meta.toml is null
- Invalid conflict stage '{}'
- snapshot checkout ref is not a local branch
- snapshot checkout commit is null
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/4e839eec6edfe47b.
Report an issue: GitHub.