jdx/mise · error

local saved history changed since planning; nothing was appl

Error message

local saved history changed since planning; nothing was applied; run pull again

What it means

validate_starting_head compares the head recorded when the plan was made against the current saved history head. If local saved history changed since planning, applying the plan could land on an unexpected base, so mise aborts with nothing applied and asks the user to pull again.

Source

Thrown at src/system/history/scope.rs:238

                warn!("history: could not persist the capture label: {err:#}");
            }
        }
    }

    /// The protective checkpoint this operation took, if any.
    pub(crate) fn before(&self) -> Option<(u64, String)> {
        self.0
            .as_ref()
            .and_then(|shared| lock_unpoisoned(shared).before.clone())
    }

    pub(crate) fn validate_starting_head(&self, expected: Option<&str>) -> Result<()> {
        let Some(shared) = &self.0 else {
            eyre::bail!("incoming application requires an active recovery operation");
        };
        let writer = lock_unpoisoned(shared);
        if writer.starting_head.as_deref() != expected {
            eyre::bail!(
                "local saved history changed since planning; nothing was applied; run pull again"
            );
        }
        Ok(())
    }

    /// Retakes the protective checkpoint, replacing the earlier one, when
    /// files changed between it and the verified plan.
    pub(crate) fn recapture_before(&self, paths: &[PathBuf]) -> Result<()> {
        let Some(shared) = &self.0 else {
            return Ok(());
        };
        let mut writer = lock_unpoisoned(shared);
        // held across the reservation, the capture, and the removal: the
        // capture writes the index and a checkpoint ref, which a concurrent
        // `mise bootstrap dotfiles save` must not interleave with
        let _store_lock = writer.store.lock()?;
        let previous = writer.before.take();

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Run pull again so a fresh plan is generated against the current head
  2. Avoid running other history operations concurrently with pull
  3. If conflicts are frequent, serialize history operations across machines/shells

Example fix

// before
mise pull   # plan created; local history changed meanwhile → error
// after
mise pull   # rerun: fresh plan against current head
Defensive patterns

Strategy: retry

Validate before calling

let current_head = history.head();
if Some(current_head.as_deref()) != planned_starting_head.as_deref() {
    eprintln!("history changed since planning; replan (pull again)");
}

Try / catch

match apply_incoming() {
    Err(e) if e.to_string().contains("run pull again") => pull_fresh_and_apply(),
    other => other,
}

Prevention

When it happens

Trigger: writer.starting_head.as_deref() != expected: another history operation committed/rolled back between planning the incoming apply and executing it.

Common situations: Two machines or two shells sharing a state dir apply history concurrently; a local rollback or commit happened after `pull` started; stale plan replayed after local history edits.

Related errors


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