{"record":{"id":"13d770957ef28d1a","repo":"jdx/mise","slug":"origin-changed-while-applying-this-setup-reconcil","errorCode":null,"errorMessage":"origin changed while applying this setup; reconcile again before publication","messagePattern":"origin changed while applying this setup; reconcile again before publication","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/sync/graph.rs","lineNumber":90,"sourceCode":"                {\n                    remote.clone()\n                } else {\n                    repo.commit_tree(tree, vec![local, remote], \"merge origin dotfiles\")?\n                }\n            }\n        };\n        Ok(Some(Candidate {\n            commit,\n            expected_local: self.local.clone(),\n            expected_remote: self.remote.clone(),\n        }))\n    }\n}\n\nimpl Candidate {\n    pub(crate) fn adopt(&self, repo: &HistoryRepo) -> Result<()> {\n        if repo.ref_oid(UPSTREAM_REF)? != self.expected_remote {\n            bail!(\"origin changed while applying this setup; reconcile again before publication\");\n        }\n        // Compare-and-swap also verifies no local boundary/save was inserted\n        // during application. The caller must recompute instead of dropping it.\n        repo.update_history_head(&self.commit, self.expected_local.as_deref())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn fresh_adoption_keeps_remote_identity_and_rejects_a_changed_fetch() {\n        let temporary = tempfile::tempdir().unwrap();\n        let repo = HistoryRepo::open_or_init_in(temporary.path())\n            .unwrap()\n            .unwrap();\n        let incoming_tree = tree(&repo, b\"incoming\");","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/history/sync/graph.rs#L72-L108","documentation":"Candidate::adopt applies a prepared history sync setup to the local history repository. Before committing, it re-reads the UPSTREAM_REF and compares it to the remote OID recorded when the candidate was computed. If the origin ref moved in the meantime, publishing would silently base history on a stale upstream, so the library refuses and demands recomputation.","triggerScenarios":"Calling Candidate::adopt (directly or via the history sync apply path) after another process or a concurrent fetch/push updated the remote branch pointed to by UPSTREAM_REF between candidate creation and adoption; expected_local no longer matches because a boundary/save commit was inserted concurrently.","commonSituations":"Two machines or two terminals syncing the same history repository simultaneously; a background fetch landed new upstream commits while the user reviewed the pending setup; a slow interactive confirm step during onboarding that spans a remote push from elsewhere.","solutions":["Re-run the reconcile/candidate computation step to build a fresh Candidate against the current UPSTREAM_REF, then adopt again","Fetch the latest origin state and rebase or merge local history onto it before re-adopting","Ensure only one sync process runs at a time (lock or serialize sync operations) to avoid racing adopt"],"exampleFix":"// before: adopting a stale candidate\nif let Ok(candidate) = compute_candidate(&repo) {\n    // ...long delay, origin moved...\n    candidate.adopt(&repo)?; // bails: origin changed\n}\n// after: recompute immediately before adopting\nlet candidate = compute_candidate(&repo)?; // reads current UPSTREAM_REF\ncandidate.adopt(&repo)?;","handlingStrategy":"retry","validationCode":"if repo.ref_oid(UPSTREAM_REF)? != candidate.expected_remote {\n    // stale candidate: recompute before adopting\n    candidate = compute_candidate(&repo)?;\n}","typeGuard":null,"tryCatchPattern":"loop {\n    match candidate.adopt(&repo) {\n        Ok(()) => break,\n        Err(e) if e.to_string().contains(\"origin changed\") => {\n            candidate = compute_candidate(&repo)?; // recompute and retry\n        }\n        Err(e) => return Err(e),\n    }\n}","preventionTips":["Serialize history sync operations with a lock so only one process computes/adopts at a time","Adopt immediately after computing a candidate; avoid long interactive delays in between","Always fetch the latest origin state right before computing a candidate"],"tags":["git","concurrency","history-sync","optimistic-concurrency"],"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"}