jdx/mise · error
history changed during the operation; cannot replace its bef
Error message
history changed during the operation; cannot replace its before state
What it means
mise's manual preimage protection verifies that the history repository's tip still points at the same tree as the recorded 'before' checkpoint. If the history branch advanced (or changed) between reading the meta and the replacement, the before state can no longer be safely swapped out, so mise aborts. This is a concurrency safety check to prevent clobbering another process's history writes.
Source
Thrown at src/system/history/checkpoint/preimages.rs:24
pub(crate) fn protect_manual_preimage(
&self,
before: &str,
tracked: &TrackedSet,
path: &Path,
prior: &PathSnapshot,
previous: &[JournalEntry],
) -> Result<Option<Box<Entry>>> {
let _lock = self.lock()?;
let repo = self
.repo
.as_ref()
.ok_or_else(|| eyre::eyre!("Git is unavailable"))?;
let mut checkpoint = repo.read_meta(before)?;
let tree = repo.output_tree_of(before)?;
let head = repo
.ref_oid(HistoryRepo::HISTORY_REF)?
.ok_or_else(|| eyre::eyre!("history branch disappeared"))?;
eyre::ensure!(
repo.output_tree_of(&head)? == tree,
"history changed during the operation; cannot replace its before state"
);
let mut promotion = Promotion {
store: self,
repo,
tracked,
previous,
overlays: BTreeMap::new(),
modes: checkpoint.tree.modes.clone(),
};
promotion.snapshot(path, prior, &tree)?;
if promotion.overlays.is_empty() && promotion.modes == checkpoint.tree.modes {
return Ok(None);
}
let overlays: Vec<_> = promotion
.overlays
.into_iter()View on GitHub (pinned to afd2eddd3a)
Solutions
- Re-run the command so it reads a fresh 'before' checkpoint and retries atomically
- Ensure only one mise process mutates history at a time (close other shells using the same data dir)
- Check for hooks, watchers, or scheduled tasks writing to the history repo
- Clear or repair the history ref if it points to an unexpected commit (mise history repair/reset)
Defensive patterns
Strategy: retry
Validate before calling
// before replacing: confirm the history tip still matches
let head = repo.ref_oid(HistoryRepo::HISTORY_REF)?.ok_or_else(|| eyre::eyre!("history branch disappeared"))?;
if repo.output_tree_of(&head)? != tree { return Err(eyre::eyre!("history changed; re-read before state")); } Try / catch
match err.message() { m if m.contains("history changed during the operation") => re_read_checkpoint_and_retry(), _ => return Err(err) } Prevention
- Don't run concurrent mise commands that mutate history in the same data dir
- Use file/branch locking around history mutations in scripts
- Re-read the checkpoint state on each attempt instead of caching it
When it happens
Trigger: Calling protect_manual_preimage when another mise process (or hook) has committed new entries to the history branch between reading the checkpoint meta and attempting the replace; or the history ref disappeared/moved.
Common situations: Running history-modifying commands concurrently in multiple shells/terminals with the same MISE_DATA_DIR; CI and local runs sharing a history store; a crash-recovery pass racing a user command.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- comparing checkpoints requires git
- listing snapshot files requires git
- local saved history changed since planning; nothing was appl
- another history operation is running: {} since {} ({})
- another history operation is running
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/56a574c215ec311d.
Report an issue: GitHub.