jdx/mise · error

saved files changed while preparing publication; plan again

Error message

saved files changed while preparing publication; plan again

What it means

`candidate` verifies that the tree of the local tip commit still matches the tree the plan was computed from. This error means the local saved files changed between planning and applying the publication — the plan is stale and must be recomputed to avoid publishing the wrong tree.

Solutions

  1. Re-run the plan (recompute the sync plan) and apply it again.
  2. Ensure no other mise sync process is running concurrently.
  3. Re-apply after the concurrent local change has been committed and included in a fresh plan.
Defensive patterns

Strategy: retry

Validate before calling

const plan = sync.plan(tree);
const tip = await sync.localTip();
if (tip && (await sync.outputTreeOf(tip)) !== tree) {
  plan = await sync.replan(); // recompute before applying
}

Type guard

function planIsCurrent(tipTree: string | null, plannedTree: string): boolean {
  return tipTree === plannedTree;
}

Try / catch

try {
  await sync.publish(plan);
} catch (e) {
  if (/changed while preparing publication/.test(String(e))) {
    await sync.publish(await sync.replan()); // retry once with a fresh plan
  } else throw e;
}

Prevention

When it happens

Trigger: Calling `candidate` with `(Some(local), None)` where `repo.output_tree_of(local)` differs from the `tree` argument — i.e. a new local commit was created after the plan was built.

Common situations: Another mise process or concurrent command committed local changes while a publication was being prepared; the user edited and re-saved dotfiles mid-operation.

Related errors


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

Appendix: source

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

                (bases.len() == 1).then(|| bases[0].clone())
            }
            _ => None,
        };
        Ok(Self {
            local,
            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()

View on GitHub (pinned to 533346cc37)