gitbutlerapp/gitbutler · critical
snapshot checkout commit {} is unavailable
Error message
snapshot checkout commit {} is unavailable What it means
After restoring snapshot contents, the recorded checkout commit must exist in the repository's object database. This bail fires when that commit is missing and was not rebuilt from snapshot data — restore cannot move HEAD to a commit it does not have.
Source
Thrown at crates/gitbutler-oplog/src/oplog.rs:1018
// check for the oid in the repo
let commit_oid = gix::ObjectId::from_hex(commit_id)?;
if !repo.has_object(commit_oid) {
// commit is not in the repo, let's build it from our data
let new_commit_oid = deserialize_commit(commit_entry.id())?;
if new_commit_oid != commit_oid {
bail!("commit id mismatch: failed to recreate a commit from its parts");
}
}
// 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,
})
});
View on GitHub (pinned to caf1f223d3)
Solutions
- Fetch the missing commit: if the branch was pushed, fetch from the remote and retry the restore
- Restore an earlier snapshot whose commits are still present
- Going forward, avoid aggressive pruning on repositories whose oplog history you may restore
Example fix
# before: restore fails with 'snapshot checkout commit ... is unavailable' # after: make the object available, then restore git fetch origin <branch-containing-the-commit> # then retry the restore in GitButler
Defensive patterns
Strategy: fallback
Validate before calling
if let Some(checkout) = snapshot_checkout(&snapshot_tree, &repo)? {
if !repo.has_object(checkout.commit_id) {
return Err(anyhow::anyhow!("checkout commit {} missing; fetch it first", checkout.commit_id));
}
} Type guard
fn snapshot_is_restorable(checkout: &SnapshotCheckout, repo: &gix::Repository) -> bool {
repo.has_object(checkout.commit_id)
} Try / catch
match oplog.restore(snapshot_id) {
Err(err) if err.to_string().contains("is unavailable") => { /* fetch remotes, then retry; else older snapshot */ }
other => other,
} Prevention
- Push work before relying on oplog snapshots
- Never run `git gc --prune=now` on repos whose oplog history you may restore
When it happens
Trigger: Restoring a snapshot whose checkout commit was garbage-collected or never present locally — objects pruned by `git gc`, shallow clones, or oplog data restored on a machine that never had the commit.
Common situations: `git gc --prune` run between snapshot and restore; restoring oplog copied without its object store; worktrees where the commit only existed on another machine.
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/cbd70a1f38ac7716.
Report an issue: GitHub.