jdx/mise · error

incoming files changed while preparing adoption; plan again

Error message

incoming files changed while preparing adoption; plan again

What it means

`candidate` verifies that the remote tip's tree still matches the tree observed when the adoption plan was built. This error means incoming origin files changed between planning and applying — the fetched remote moved, so the adoption plan is stale and must be recomputed.

Solutions

  1. Recompute the adoption plan against the freshly fetched remote and apply again.
  2. Serialize sync operations so fetch/apply don't interleave.
  3. Re-fetch and verify the remote tip is stable before applying the plan.
Defensive patterns

Strategy: retry

Validate before calling

const remoteTree = await sync.outputTreeOf(remoteTip);
if (remoteTree !== plannedTree) {
  await sync.refetch();
  plan = await sync.replan();
}

Type guard

function remoteUnchanged(remoteTree: string, plannedTree: string): boolean {
  return remoteTree === plannedTree;
}

Try / catch

try {
  await sync.adopt(plan);
} catch (e) {
  if (/changed while preparing adoption/.test(String(e))) {
    await sync.refetchAndReplan(); // retry against the new remote tip
  } else throw e;
}

Prevention

When it happens

Trigger: Calling `candidate` with `(None, Some(remote))` where `repo.output_tree_of(remote)` differs from the `tree` argument — i.e. new commits were fetched into UPSTREAM_REF after the plan was made.

Common situations: Another machine pushed to origin and a fetch happened between planning and applying; running two sync operations concurrently; a scheduled fetch landed mid-adoption.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/e2da3369a4a9f6c1. Report an issue: GitHub.

Appendix: source

Thrown at src/system/history/sync/graph.rs:61

            remote,
            base,
        })
    }

    /// Preparing a candidate does not advance a branch or write live files.
    /// The caller must finish its complete application before adopting it.
    pub(crate) fn candidate(&self, repo: &HistoryRepo, tree: &str) -> Result<Option<Candidate>> {
        let commit = match (&self.local, &self.remote) {
            (None, None) => return Ok(None),
            (Some(local), None) => {
                if repo.output_tree_of(local)? != tree {
                    bail!("saved files changed while preparing publication; plan again");
                }
                local.clone()
            }
            (None, Some(remote)) => {
                if repo.output_tree_of(remote)? != tree {
                    bail!("incoming files changed while preparing adoption; plan again");
                }
                remote.clone()
            }
            (Some(local), Some(remote)) => {
                if self.base.as_ref() == Some(remote) {
                    if repo.output_tree_of(local)? != tree {
                        bail!("saved files changed while preparing publication; plan again");
                    }
                    local.clone()
                } else if self.base.as_ref() == Some(local) && repo.output_tree_of(remote)? == tree
                {
                    remote.clone()
                } else {
                    repo.commit_tree(tree, vec![local, remote], "merge origin dotfiles")?
                }
            }
        };
        Ok(Some(Candidate {

View on GitHub (pinned to 533346cc37)