jdx/mise · error

setup history changed during adoption; retry pull

Error message

setup history changed during adoption; retry pull

What it means

During `mise dot sync` pull, apply_locked_with_scope re-reads the shadow-history heads after writing directories and verifies they still match the commit that was planned (application_head and the upstream commit). If another process (user edit, concurrent sync, GC) moved the local or remote head between planning and finalization, mise aborts with this error rather than applying a plan computed against stale history. It is a deliberate optimistic-concurrency check protecting against applying a pull onto history that has been rewritten underneath it.

Solutions

  1. Simply re-run `mise dot sync` (pull) — the message explicitly says retry; the new plan will be computed against the current heads.
  2. Ensure only one sync runs at a time (serialize syncs, or rely on mise's lock if available) so concurrent history writes stop racing.
  3. Avoid manually mutating the shadow history repo (git reset/prune/rebase) while a sync is active.
  4. If it recurs without concurrency, file a bug; it may indicate two code paths committing history without coordination.

Example fix

// before: two terminals racing
# terminal A: mise dot sync &
# terminal B: mise dot sync   # -> setup history changed during adoption; retry pull
// after
# terminal A: mise dot sync
# terminal B: wait for A, then: mise dot sync
Defensive patterns

Strategy: retry

Validate before calling

// before pulling, check no other sync is running
pgrep -f 'mise dot sync' && echo 'another sync in progress — wait' || mise dot sync

Try / catch

// retry with backoff on the specific message
for attempt in 1..=3 {
  match run_sync() {
    Ok(()) => break,
    Err(e) if e.contains("setup history changed during adoption") => sleep(backoff(attempt)),
    Err(e) => return Err(e),
  }
}

Prevention

When it happens

Trigger: Another `mise dot sync` (pull or push) or history-writing process commits to the shadow repo while a pull is being applied, so Heads::read at apply.rs:676 returns heads.local != application_head or heads.remote != status.upstream_commit. Also caused by external mutation of the shadow history store (manual git commands in the history repo, history GC/pruning) mid-pull.

Common situations: Running `mise dot sync` in two terminals at once; an editor/daemon that touches dotfiles while a slow pull is in flight; running history maintenance (prune/rebase of the history repo) concurrently with a pull.

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/649f2480150ac146. Report an issue: GitHub.

Appendix: source

Thrown at src/system/history/sync/apply.rs:676

        if fresh_adoption || inventory_tree.is_some() {
            // Verify the whole written batch again before advancing Git. A
            // concurrent edit must not become an apparently applied setup.
            for step in &ready {
                if live_object(repo, &step.path)? != step.pending.object
                    || live_permissions(&step.path)? != step.desired_mode
                {
                    bail!(
                        "{} changed during setup adoption; retry pull",
                        display_path(&step.path)
                    );
                }
            }
            for directory in &directories {
                directory.verify_written()?;
            }
            let heads = super::graph::Heads::read(repo)?;
            if heads.local != application_head || heads.remote != status.upstream_commit {
                bail!("setup history changed during adoption; retry pull");
            }
            let remote = heads.remote.as_deref().unwrap();
            audit_incoming_history(repo, remote)?;
            let tree = inventory_tree
                .clone()
                .unwrap_or(repo.output_tree_of(remote)?);
            let candidate = heads
                .candidate(repo, &tree)?
                .ok_or_else(|| eyre::eyre!("setup branch disappeared during adoption"))?;
            candidate.adopt(repo)?;
        }
        Ok(())
    })();
    if let Err(error) = &result {
        let mut recovery_errors = vec![];
        for step in ready
            .iter()
            .rev()

View on GitHub (pinned to 533346cc37)