{"record":{"id":"56a574c215ec311d","repo":"jdx/mise","slug":"history-changed-during-the-operation-cannot-repla","errorCode":null,"errorMessage":"history changed during the operation; cannot replace its before state","messagePattern":"history changed during the operation; cannot replace its before state","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/checkpoint/preimages.rs","lineNumber":24,"sourceCode":"    pub(crate) fn protect_manual_preimage(\n        &self,\n        before: &str,\n        tracked: &TrackedSet,\n        path: &Path,\n        prior: &PathSnapshot,\n        previous: &[JournalEntry],\n    ) -> Result<Option<Box<Entry>>> {\n        let _lock = self.lock()?;\n        let repo = self\n            .repo\n            .as_ref()\n            .ok_or_else(|| eyre::eyre!(\"Git is unavailable\"))?;\n        let mut checkpoint = repo.read_meta(before)?;\n        let tree = repo.output_tree_of(before)?;\n        let head = repo\n            .ref_oid(HistoryRepo::HISTORY_REF)?\n            .ok_or_else(|| eyre::eyre!(\"history branch disappeared\"))?;\n        eyre::ensure!(\n            repo.output_tree_of(&head)? == tree,\n            \"history changed during the operation; cannot replace its before state\"\n        );\n        let mut promotion = Promotion {\n            store: self,\n            repo,\n            tracked,\n            previous,\n            overlays: BTreeMap::new(),\n            modes: checkpoint.tree.modes.clone(),\n        };\n        promotion.snapshot(path, prior, &tree)?;\n        if promotion.overlays.is_empty() && promotion.modes == checkpoint.tree.modes {\n            return Ok(None);\n        }\n        let overlays: Vec<_> = promotion\n            .overlays\n            .into_iter()","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/history/checkpoint/preimages.rs#L6-L42","documentation":"`protect_manual_preimage` in the history checkpoint module validates that the history repo's HEAD output tree has not moved between reading the checkpoint's `before` tree and replacing it. If the history branch ref changed concurrently (another mise process committed, or a gc/rebase ran), replacing the preimage would corrupt history, so it aborts.","triggerScenarios":"Calling `protect_manual_preimage` while another process updates the history branch between `output_tree_of(before)` and the `ref_oid(HISTORY_REF)` comparison, causing the two tree OIDs to differ.","commonSituations":"Two mise sessions running history-tracking commands concurrently; a background checkpoint/garbage-collect advanced the history ref; a user manually committed to the history repo.","solutions":["Re-run the operation after other mise history processes finish (the failure is transient concurrency).","Inspect the history repo for unexpected commits between the two tree states (`git log history-ref`) and reconcile or reset unwanted changes.","Avoid manually editing the history branch; let mise manage it exclusively."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"let tree = repo.output_tree_of(before)?;\nlet head = repo.ref_oid(HistoryRepo::HISTORY_REF)?;\nif repo.output_tree_of(&head?)? != tree { /* history moved: re-read and retry */ }","typeGuard":null,"tryCatchPattern":"// retry with backoff when history moved\nmatch protect_manual_preimage(...) {\n    Err(e) if e.to_string().contains(\"history changed\") => retry_after_delay(),\n    other => other,\n}","preventionTips":["Avoid running multiple concurrent mise history-mutating commands.","Never manually commit to the history branch.","Serialize history operations with a lock when scripting mise."],"tags":["git","history","concurrency","state-conflict"],"backgroundTag":"invalid-state-transition","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}