jdx/mise · error
origin changed while applying this setup; reconcile again be
Error message
origin changed while applying this setup; reconcile again before publication
What it means
`Candidate::adopt` in src/system/history/sync/graph.rs performs a compare-and-swap when applying a reconciliation plan: before advancing the history head it re-reads the upstream ref (`refs/remotes/origin/setup`) and verifies it still points at the OID the candidate was built against. If the origin moved between planning and adoption, the library refuses to apply, because the plan's merge or fast-forward may no longer be correct. This is an optimistic-concurrency guard; the caller must fetch and recompute instead of dropping or forcing the plan.
Source
Thrown at src/system/history/sync/graph.rs:90
{
remote.clone()
} else {
repo.commit_tree(tree, vec![local, remote], "merge origin dotfiles")?
}
}
};
Ok(Some(Candidate {
commit,
expected_local: self.local.clone(),
expected_remote: self.remote.clone(),
}))
}
}
impl Candidate {
pub(crate) fn adopt(&self, repo: &HistoryRepo) -> Result<()> {
if repo.ref_oid(UPSTREAM_REF)? != self.expected_remote {
bail!("origin changed while applying this setup; reconcile again before publication");
}
// Compare-and-swap also verifies no local boundary/save was inserted
// during application. The caller must recompute instead of dropping it.
repo.update_history_head(&self.commit, self.expected_local.as_deref())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fresh_adoption_keeps_remote_identity_and_rejects_a_changed_fetch() {
let temporary = tempfile::tempdir().unwrap();
let repo = HistoryRepo::open_or_init_in(temporary.path())
.unwrap()
.unwrap();
let incoming_tree = tree(&repo, b"incoming");View on GitHub (pinned to afd2eddd3a)
Solutions
- Fetch the origin again and rebuild the plan: run `Heads::read`, recompute the candidate, and `adopt()` the fresh candidate — the message says to 'reconcile again before publication'.
- Minimize the window between `candidate()` and `adopt()`; re-plan immediately after resolving conflicts.
- Coordinate with the other machine/user pushing to the setup branch, or push promptly after applying to reduce divergence.
- Do not force-update the history head or the origin branch; forced publication is not supported and would bypass this guard.
Example fix
// before: adopt a candidate built long ago
candidate.adopt(&repo)?; // origin may have advanced meanwhile
// after: re-plan and adopt atomically-ish
let heads = Heads::read(&repo)?;
if let Some(fresh) = heads.candidate(&repo, &tree)? {
fresh.adopt(&repo)?;
} Defensive patterns
Strategy: retry
Validate before calling
// verify the origin ref still matches what the plan expects, before adopting
if repo.ref_oid(UPSTREAM_REF)? != candidate.expected_remote {
// fetch and rebuild the plan instead of adopting
} Try / catch
// Rust
match candidate.adopt(&repo) {
Ok(()) => {/* published */},
Err(e) if e.to_string().contains("origin changed") => {
fetch_origin()?;
let heads = Heads::read(&repo)?;
let fresh = heads.candidate(&repo, &tree)?;
if let Some(fresh) = fresh { fresh.adopt(&repo)?; }
}
Err(e) => return Err(e),
} Prevention
- Adopt immediately after building the candidate; don't hold plans across user-interactive conflict resolution
- Pause periodic background fetches while applying, or re-fetch right before adopting
- Coordinate pushes to the shared setup branch (push promptly, keep sessions short)
- Never force-update the origin branch; always reconcile via a fresh plan
When it happens
Trigger: Calling `candidate.adopt(&repo)` when `repo.ref_oid(UPSTREAM_REF)` no longer equals `candidate.expected_remote` — i.e. a fetch updated `refs/remotes/origin/setup` (someone else pushed to the origin setup branch) after `Heads::candidate()` created this candidate but before `adopt()` ran. `repo.update_history_head` can also fail its own compare-and-swap if a local save was inserted in the same window.
Common situations: A teammate pushed new setup commits to the shared origin while you were applying a setup; the periodic background fetch landed mid-apply; a long conflict-resolution session allowed the remote to advance; two machines syncing the same setup repository simultaneously.
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
- setup history changed during adoption; retry pull
- incoming files changed while preparing adoption; plan again
- {} changed while preparing enrollment; concurrent declaratio
- changed while preparing recovery; left untouched
- {} changed before application; nothing was written
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/13d770957ef28d1a.
Report an issue: GitHub.