jdx/mise · error

origin changed while planning; reconcile again

Error message

origin changed while planning; reconcile again

What it means

During `prepare`, mise plans the directory changes against a snapshot of the upstream. After planning, it re-checks that the remote head (`heads.remote`) still equals the upstream commit it planned against; if the origin moved mid-operation (another machine pushed while this sync was planning), the plan is stale and mise aborts, asking for a fresh reconciliation. This is a deliberate optimistic-concurrency check.

Source

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

    // 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 afd2eddd3a)

Solutions

  1. Simply re-run `mise bootstrap dotfiles sync` — it fetches the new head and replans from scratch
  2. Ensure only one sync runs at a time (see the status-lock) so competing machines don't interleave
  3. If another machine is the mover, coordinate pushes/pulls rather than syncing simultaneously
Defensive patterns

Strategy: retry

Try / catch

catch (e) { if (String(e).includes('origin changed while planning')) { return syncAgain(); } throw e; }

Prevention

When it happens

Trigger: `mise bootstrap dotfiles sync` (or `refresh_with_interaction`) where, between the fetch and the end of planning (`super::directories::plan`), the remote branch advanced so `heads.remote != upstream.commit`.

Common situations: Another machine or CI pushed to the setup branch during a long local sync; a background scheduled sync raced an interactive one; network flakiness causing re-fetch of a moved ref mid-run.

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