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

  1. Restore the next-older oplog entry — one entry back is usually consistent
  2. Report with the snapshot id; managed snapshots should be written atomically, so disagreement indicates a bug
  3. 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

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


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