{"record":{"id":"1b6e5eb46b44df45","repo":"jdx/mise","slug":"origin-changed-while-planning-reconcile-again","errorCode":null,"errorMessage":"origin changed while planning; reconcile again","messagePattern":"origin changed while planning; reconcile again","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/sync/run.rs","lineNumber":678,"sourceCode":"    // turn an ordinary incoming edit into an unrelated adoption conflict.\n    let heads = super::graph::Heads::read(repo)?;\n    status.upstream_commit = heads.remote.clone();\n    status.pending_repository = match &heads.local {\n        Some(local) => {\n            crate::system::history::manifest::Manifest::read(repo, local)?.as_ref()\n                != Some(&tracked.manifest)\n        }\n        None => heads.remote.is_some(),\n    };\n    let repository_tree = incoming_repository_tree(repo, tracked)\n        .inspect_err(|error| repository_conflict(status, error))?;\n    status.pending_repository |= repository_tree.is_some();\n    if let Some(tree) = repository_tree.as_deref().or(heads.remote.as_deref()) {\n        super::directories::plan(repo, tracked, tree)\n            .inspect_err(|error| repository_conflict(status, error))?;\n    }\n    if heads.remote != upstream.commit {\n        bail!(\"origin changed while planning; reconcile again\");\n    }\n    let baseline = reconcile::upstream(repo, heads.base.as_deref())?;\n    let local_manifest = heads\n        .local\n        .as_deref()\n        .map(|head| crate::system::history::manifest::Manifest::read(repo, head))\n        .transpose()?\n        .flatten()\n        .unwrap_or_default();\n    let sync_state: state::SyncState = baseline\n        .files\n        .into_iter()\n        .map(|(path, object)| {\n            (\n                path,\n                state::SyncRecord {\n                    acknowledged: Some(object.clone()),\n                    reconciled: Some(object.clone()),","sourceCodeStart":660,"sourceCodeEnd":696,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/sync/run.rs#L660-L696","documentation":"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.","triggerScenarios":"`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`.","commonSituations":"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.","solutions":["Simply re-run `mise bootstrap dotfiles sync` — it fetches the new head and replans from scratch","Ensure only one sync runs at a time (see the status-lock) so competing machines don't interleave","If another machine is the mover, coordinate pushes/pulls rather than syncing simultaneously"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"catch (e) { if (String(e).includes('origin changed while planning')) { return syncAgain(); } throw e; }","preventionTips":["Avoid running sync on two machines against the same branch simultaneously","Keep sync operations short so the plan window is small"],"tags":["git","sync","race-condition","optimistic-concurrency"],"backgroundTag":"invalid-state-transition","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}