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

  1. Re-run the command so it reads a fresh 'before' checkpoint and retries atomically
  2. Ensure only one mise process mutates history at a time (close other shells using the same data dir)
  3. Check for hooks, watchers, or scheduled tasks writing to the history repo
  4. 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

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


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/56a574c215ec311d. Report an issue: GitHub.