{"record":{"id":"654510ff8117ac5d","repo":"jdx/mise","slug":"local-saved-history-changed-while-planning-reconc","errorCode":null,"errorMessage":"local saved history changed while planning; reconcile again","messagePattern":"local saved history changed while planning; reconcile again","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/sync/publish.rs","lineNumber":21,"sourceCode":"use eyre::{Result, bail};\nuse std::collections::BTreeSet;\n\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    allow_plaintext_history: bool,\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.","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/history/sync/publish.rs#L3-L39","documentation":"Thrown in build (publish path) when the locally saved history HEAD read now differs from the expected_local hash captured earlier during planning. It is an optimistic-concurrency guard: something modified the local history ref between planning and publication, so the prepared batch is stale and must be recomputed to avoid clobbering changes.","triggerScenarios":"Running a publish whose Heads::read(repo).local no longer equals the expected_local passed from the planning phase — i.e. another mise process or manual git ref update moved the local history branch in between.","commonSituations":"Two concurrent `mise` history-sync operations (e.g. two shells); a background reconcile process updating history between plan and publish; manually resetting the history branch with git while a publish was prepared.","solutions":["Re-run the sync/pull so planning and publication happen on the current state","Ensure no other mise process is running history sync concurrently","If the local history ref was moved manually, restore it or accept the new state and re-plan","Retry the publish after reconciliation"],"exampleFix":"# before: publish prepared earlier against old local head\nmise dot sync publish   # fails if head moved\n# after: reconcile and retry with fresh expectations\nmise dot sync reconcile && mise dot sync publish","handlingStrategy":"retry","validationCode":"let heads = Heads::read(repo)?;\nif heads.local.as_deref() != expected_local {\n    eprintln!(\"history moved; re-plan before publishing\");\n}","typeGuard":null,"tryCatchPattern":"match build(...) {\n    Err(e) if e.to_string().contains(\"changed while planning\") => {\n        // re-read heads, recompute plan, retry once\n    }\n    other => other?,\n}","preventionTips":["Avoid running concurrent mise history-sync operations","Reconcile and re-plan immediately before publishing instead of using stale plans","Do not manually move/reset history refs while a sync is in progress"],"tags":["history-sync","concurrency","git"],"backgroundTag":"invalid-state-transition","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}