{"record":{"id":"72aebf4185dbda43","repo":"jdx/mise","slug":"origin-changed-while-preparing-publication-reconc","errorCode":null,"errorMessage":"origin changed while preparing publication; reconcile again","messagePattern":"origin changed while preparing publication; reconcile again","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/sync/publish.rs","lineNumber":23,"sourceCode":"\nuse super::graph::Heads;\nuse super::network::{PushOutcome, Remote};\nuse crate::system::history::shadow::HistoryRepo;\n\n/// Reconciliation and complete live application must precede publication.\n/// A merge that still changes this machine's saved tree needs another pull.\npub(crate) fn build(\n    repo: &HistoryRepo,\n    upstream: Option<&str>,\n    expected_local: Option<&str>,\n    accepted: &BTreeSet<String>,\n) -> Result<Option<String>> {\n    let heads = Heads::read(repo)?;\n    if heads.local.as_deref() != expected_local {\n        bail!(\"local saved history changed while planning; reconcile again\");\n    }\n    if heads.remote.as_deref() != upstream {\n        bail!(\"origin changed while preparing publication; reconcile again\");\n    }\n    let Some(local) = &heads.local else {\n        return Ok(None);\n    };\n    if heads.remote.as_ref() == Some(local) {\n        return Ok(None);\n    }\n    let tree = repo.output_tree_of(local)?;\n    if let Some(remote) = &heads.remote\n        && heads.base.as_ref() != Some(remote)\n    {\n        let (mut merged, mut conflicts) = repo.merge_tree(local, remote)?;\n        // Enrollment is a keyed inventory, not arbitrary JSON text. Git's\n        // line merge can conflict on independent additions or combine policy\n        // changes into invalid metadata. Validate its structured merge first.\n        if let Some(base) = &heads.base {\n            use crate::system::history::manifest::Manifest;\n            if let (Some(base), Some(ours), Some(theirs)) = (","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/sync/publish.rs#L5-L41","documentation":"Analogous to the local-head check, mise verifies the remote/origin head has not moved since the plan was built. If `heads.remote != upstream` (someone else pushed to the shared history repository between planning and publication), mise aborts and requires a fresh reconcile so the push is based on the current upstream state.","triggerScenarios":"In publish `build`, `Heads::read(repo).remote != upstream`. Triggered when a teammate pushed new history to the origin between the fetch/plan step and the publish step, or another local process fetched and advanced the recorded remote head.","commonSituations":"Active team pushing to the shared history repo while you publish; a long gap between pull and publish during which others pushed; a scheduled fetch job updating the remote ref.","solutions":["Pull/fetch again to observe the new origin state and rebuild the plan, then publish.","Resolve any new conflicts the updated origin introduces (see the sync-paused flow), then publish.","Reduce the window between plan and publish, or coordinate pushes within the team."],"exampleFix":"// before: teammate pushed while you prepared\nmise bootstrap dotfiles publish   # origin changed -> abort\n// after\nmise bootstrap dotfiles pull      # reconcile against new origin\nmise bootstrap dotfiles publish","handlingStrategy":"retry","validationCode":"const heads = await readHeads();\nif (heads.remote !== expectedUpstream) {\n  console.warn(\"origin moved; fetch and reconcile before publishing\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  await publish();\n} catch (e) {\n  if (String(e.message).includes(\"origin changed while preparing\")) {\n    await pull();          // fetch + merge with new origin\n    await publish();       // retry against updated upstream\n  } else throw e;\n}","preventionTips":["Pull immediately before publishing to shrink the race window.","Coordinate with teammates around shared-history pushes.","Retry the publish loop (pull -> publish) until it succeeds, bounding retries."],"tags":["sync","race-condition","git","upstream"],"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"}