jdx/mise · error

local saved history changed while planning; reconcile again

Error message

local saved history changed while planning; reconcile again

What it means

Thrown in build (publish path) when the locally saved history HEAD read now differs from the expected_local hash captured earlier during planning. It is an optimistic-concurrency guard: something modified the local history ref between planning and publication, so the prepared batch is stale and must be recomputed to avoid clobbering changes.

Solutions

  1. Re-run the sync/pull so planning and publication happen on the current state
  2. Ensure no other mise process is running history sync concurrently
  3. If the local history ref was moved manually, restore it or accept the new state and re-plan
  4. Retry the publish after reconciliation

Example fix

# before: publish prepared earlier against old local head
mise dot sync publish   # fails if head moved
# after: reconcile and retry with fresh expectations
mise dot sync reconcile && mise dot sync publish
Defensive patterns

Strategy: retry

Validate before calling

let heads = Heads::read(repo)?;
if heads.local.as_deref() != expected_local {
    eprintln!("history moved; re-plan before publishing");
}

Try / catch

match build(...) {
    Err(e) if e.to_string().contains("changed while planning") => {
        // re-read heads, recompute plan, retry once
    }
    other => other?,
}

Prevention

When it happens

Trigger: Running a publish whose Heads::read(repo).local no longer equals the expected_local passed from the planning phase — i.e. another mise process or manual git ref update moved the local history branch in between.

Common situations: Two concurrent `mise` history-sync operations (e.g. two shells); a background reconcile process updating history between plan and publish; manually resetting the history branch with git while a publish was prepared.

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@533346cc37 (2026-09-17). Data as JSON: /api/errors/654510ff8117ac5d. Report an issue: GitHub.

Appendix: source

Thrown at src/system/history/sync/publish.rs:21

use eyre::{Result, bail};
use std::collections::BTreeSet;

use super::graph::Heads;
use super::network::{PushOutcome, Remote};
use crate::system::history::shadow::HistoryRepo;

/// Reconciliation and complete live application must precede publication.
/// A merge that still changes this machine's saved tree needs another pull.
pub(crate) fn build(
    repo: &HistoryRepo,
    upstream: Option<&str>,
    expected_local: Option<&str>,
    accepted: &BTreeSet<String>,
    allow_plaintext_history: bool,
) -> Result<Option<String>> {
    let heads = Heads::read(repo)?;
    if heads.local.as_deref() != expected_local {
        bail!("local saved history changed while planning; reconcile again");
    }
    if heads.remote.as_deref() != upstream {
        bail!("origin changed while preparing publication; reconcile again");
    }
    let Some(local) = &heads.local else {
        return Ok(None);
    };
    if heads.remote.as_ref() == Some(local) {
        return Ok(None);
    }
    let tree = repo.output_tree_of(local)?;
    if let Some(remote) = &heads.remote
        && heads.base.as_ref() != Some(remote)
    {
        let (mut merged, mut conflicts) = repo.merge_tree(local, remote)?;
        // Enrollment is a keyed inventory, not arbitrary JSON text. Git's
        // line merge can conflict on independent additions or combine policy
        // changes into invalid metadata. Validate its structured merge first.

View on GitHub (pinned to 533346cc37)