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

  1. Fetch the missing commit: if the branch was pushed, fetch from the remote and retry the restore
  2. Restore an earlier snapshot whose commits are still present
  3. 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

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


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/cbd70a1f38ac7716. Report an issue: GitHub.