{"record":{"id":"4e839eec6edfe47b","repo":"gitbutlerapp/gitbutler","slug":"snapshot-checkout-and-workspace-commits-disagree","errorCode":null,"errorMessage":"snapshot checkout and workspace commits disagree","messagePattern":"snapshot checkout and workspace commits disagree","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/gitbutler-oplog/src/oplog.rs","lineNumber":1026,"sourceCode":"            }\n\n            // TODO: in the next iteration, this of course can't be hardcoded.\n            if branch_name == \"workspace\" {\n                restored_workspace_commit = Some(commit_oid);\n            }\n        }\n    }\n    if let Some(checkout) = restored_checkout.as_ref() {\n        if !repo.has_object(checkout.commit_id) {\n            bail!(\n                \"snapshot checkout commit {} is unavailable\",\n                checkout.commit_id\n            );\n        }\n        if checkout.ref_name.as_ref() == workspace_ref\n            && restored_workspace_commit != Some(checkout.commit_id)\n        {\n            bail!(\"snapshot checkout and workspace commits disagree\");\n        }\n    }\n    // Managed snapshots already identify their checkout through the workspace commit entry.\n    let restored_checkout = restored_checkout.or_else(|| {\n        restored_workspace_commit.map(|commit_id| SnapshotCheckout {\n            ref_name: workspace_ref.to_owned(),\n            commit_id,\n        })\n    });\n\n    let head = repo.head()?;\n    let head_ref = head\n        .referent_name()\n        .context(\"We will not change a worktree in detached HEAD state\")?;\n    // Snapshots with neither checkout identity nor a workspace commit retain the old guard.\n    if restored_checkout.is_none() && head_ref != workspace_ref {\n        bail!(\"cannot restore a snapshot without checkout identity outside the workspace branch\");\n    }","sourceCodeStart":1008,"sourceCodeEnd":1044,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/gitbutler-oplog/src/oplog.rs#L1008-L1044","documentation":"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.","triggerScenarios":"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.","commonSituations":"App killed mid-snapshot-write; oplog trees edited or restored piecemeal; version migrations assembling snapshots incorrectly.","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"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"if checkout.ref_name.as_ref() == workspace_ref\n    && restored_workspace_commit != Some(checkout.commit_id)\n{\n    return Err(anyhow::anyhow!(\"snapshot is internally inconsistent; skip it\"));\n}","typeGuard":null,"tryCatchPattern":"match oplog.restore(snapshot_id) {\n    Err(err) if err.to_string().contains(\"commits disagree\") => { /* step one entry back in the oplog */ }\n    other => other,\n}","preventionTips":["Let the app exit cleanly so oplog snapshot writes finish atomically","Validate snapshot consistency in restore-path tests"],"tags":["rust","gitbutler","oplog","restore","consistency","workspace"],"backgroundTag":"corrupt-snapshot-data","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}