jdx/mise · error

origin changed while planning; reconcile again

Error message

origin changed while planning; reconcile again

What it means

During prepare, mise re-checks that the origin head recorded in the sync status (upstream.commit) still matches the remote head read from the repository (heads.remote) after planning directory changes. If they diverged, the plan is based on a stale view of origin, so mise aborts and asks for a fresh reconcile.

Solutions

  1. Re-run `mise dot sync` so planning happens with freshly fetched heads.
  2. Ensure no other sync/pull is running concurrently, then retry.
  3. If origin genuinely moved, run `mise dot pull` first to reconcile with the new origin head, then sync.

Example fix

// before: origin moved during prepare
mise dot sync // error: origin changed while planning
// after: reconcile first, then sync
mise dot pull
mise dot sync
Defensive patterns

Strategy: retry

Validate before calling

// confirm origin head still matches the recorded status commit before syncing
let heads = Heads::read(repo)?;
if heads.remote != upstream.commit {
    eprintln!("origin moved; run `mise dot pull` first");
    return;
}

Type guard

fn origin_consistent(heads: &Heads, upstream: &Upstream) -> bool {
    heads.remote == upstream.commit
}

Try / catch

match sync() {
    Err(e) if e.to_string().contains("origin changed while planning") => { pull(); sync(); }
    other => other,
}

Prevention

When it happens

Trigger: Running sync/pull where the remote head moved (or the status commit changed) between reading status and planning — e.g. a concurrent fetch/pull updated heads, or another machine pushed to origin while this prepare was running.

Common situations: Another machine pushed to the setup branch mid-sync; a concurrent `mise dot pull` refreshed heads while a sync was preparing; reconnecting to a different origin without re-pulling first.

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/1b6e5eb46b44df45. Report an issue: GitHub.

Appendix: source

Thrown at src/system/history/sync/run.rs:701

    // turn an ordinary incoming edit into an unrelated adoption conflict.
    let heads = super::graph::Heads::read(repo)?;
    status.upstream_commit = heads.remote.clone();
    status.pending_repository = match &heads.local {
        Some(local) => {
            crate::system::history::manifest::Manifest::read(repo, local)?.as_ref()
                != Some(&tracked.manifest)
        }
        None => heads.remote.is_some(),
    };
    let repository_tree = incoming_repository_tree(repo, tracked)
        .inspect_err(|error| repository_conflict(status, error))?;
    status.pending_repository |= repository_tree.is_some();
    if let Some(tree) = repository_tree.as_deref().or(heads.remote.as_deref()) {
        super::directories::plan(repo, tracked, tree)
            .inspect_err(|error| repository_conflict(status, error))?;
    }
    if heads.remote != upstream.commit {
        bail!("origin changed while planning; reconcile again");
    }
    let baseline = reconcile::upstream(repo, heads.base.as_deref())?;
    let local_manifest = heads
        .local
        .as_deref()
        .map(|head| crate::system::history::manifest::Manifest::read(repo, head))
        .transpose()?
        .flatten()
        .unwrap_or_default();
    let sync_state: state::SyncState = baseline
        .files
        .into_iter()
        .map(|(path, object)| {
            (
                path,
                state::SyncRecord {
                    acknowledged: Some(object.clone()),
                    reconciled: Some(object.clone()),

View on GitHub (pinned to 533346cc37)