gitbutlerapp/gitbutler · error
snapshot checkout ref is not a local branch
Error message
snapshot checkout ref is not a local branch
What it means
A snapshot records which branch and commit was checked out; on restore the checkout ref must be a local branch (`refs/heads/...`) because GitButler only restores checkouts of local branches. This bail fires when the recorded ref parses as a valid ref name but belongs to another category (remote-tracking ref, tag, or notes ref).
Source
Thrown at crates/gitbutler-oplog/src/oplog.rs:540
}
fn snapshot_checkout(
snapshot_tree: &gix::Tree<'_>,
repo: &gix::Repository,
) -> Result<Option<SnapshotCheckout>> {
let Some(ref_entry) = snapshot_tree.lookup_entry_by_path("checkout/ref")? else {
return Ok(None);
};
let commit_entry = snapshot_tree
.lookup_entry_by_path("checkout/commit")?
.context("snapshot checkout ref has no commit")?;
let ref_blob = repo
.find_blob(ref_entry.id())
.context("failed to read snapshot checkout ref")?;
let ref_name = gix::refs::FullName::try_from(ref_blob.data.as_bstr())
.context("snapshot checkout ref is invalid")?;
if ref_name.category() != Some(gix::refs::Category::LocalBranch) {
bail!("snapshot checkout ref is not a local branch");
}
let commit_blob = repo
.find_blob(commit_entry.id())
.context("failed to read snapshot checkout commit")?;
let commit_id = gix::ObjectId::from_hex(&commit_blob.data)
.context("snapshot checkout commit is invalid")?;
if commit_id.is_null() {
bail!("snapshot checkout commit is null");
}
Ok(Some(SnapshotCheckout {
ref_name,
commit_id,
}))
}
fn snapshot_metadata(
snapshot_tree: &gix::Tree<'_>,
repo: &gix::Repository,View on GitHub (pinned to caf1f223d3)
Solutions
- Fix the snapshot's `checkout/ref` to a local branch name and retry the restore
- Restore an earlier snapshot with a valid checkout record
- Report as a bug if the snapshot was produced by the same app version
Defensive patterns
Strategy: validation
Validate before calling
let ref_name = gix::refs::FullName::try_from(ref_blob.data.as_bstr())?;
if ref_name.category() != Some(gix::refs::Category::LocalBranch) {
return Err(anyhow::anyhow!("snapshot checkout ref {ref_name} is not a local branch; refusing"));
} Type guard
fn is_local_branch(ref_name: &gix::refs::FullName) -> bool {
ref_name.category() == Some(gix::refs::Category::LocalBranch)
} Try / catch
match oplog.restore(snapshot_id) {
Err(err) if err.to_string().contains("not a local branch") => { /* pick another snapshot */ }
other => other,
} Prevention
- Validate the ref category when writing checkout records
- Don't hand-craft snapshot trees
When it happens
Trigger: Restoring a snapshot whose `checkout/ref` blob contains e.g. `refs/remotes/origin/main` or `refs/tags/v1` instead of a `refs/heads/` name.
Common situations: Hand-edited or migrated snapshot trees; writers storing the wrong ref kind; snapshots produced by incompatible versions.
Related errors
- targetRef in project_meta.toml is not a remote-tracking bran
- snapshot checkout commit is null
- targetCommitId in project_meta.toml is null
- Invalid conflict stage '{}'
- commit id mismatch: failed to recreate a commit from its par
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/ffd895a617275756.
Report an issue: GitHub.