{"record":{"id":"649f2480150ac146","repo":"jdx/mise","slug":"setup-history-changed-during-adoption-retry-pull","errorCode":null,"errorMessage":"setup history changed during adoption; retry pull","messagePattern":"setup history changed during adoption; retry pull","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/sync/apply.rs","lineNumber":676,"sourceCode":"        if fresh_adoption || inventory_tree.is_some() {\n            // Verify the whole written batch again before advancing Git. A\n            // concurrent edit must not become an apparently applied setup.\n            for step in &ready {\n                if live_object(repo, &step.path)? != step.pending.object\n                    || live_permissions(&step.path)? != step.desired_mode\n                {\n                    bail!(\n                        \"{} changed during setup adoption; retry pull\",\n                        display_path(&step.path)\n                    );\n                }\n            }\n            for directory in &directories {\n                directory.verify_written()?;\n            }\n            let heads = super::graph::Heads::read(repo)?;\n            if heads.local != application_head || heads.remote != status.upstream_commit {\n                bail!(\"setup history changed during adoption; retry pull\");\n            }\n            let remote = heads.remote.as_deref().unwrap();\n            audit_incoming_history(repo, remote)?;\n            let tree = inventory_tree\n                .clone()\n                .unwrap_or(repo.output_tree_of(remote)?);\n            let candidate = heads\n                .candidate(repo, &tree)?\n                .ok_or_else(|| eyre::eyre!(\"setup branch disappeared during adoption\"))?;\n            candidate.adopt(repo)?;\n        }\n        Ok(())\n    })();\n    if let Err(error) = &result {\n        let mut recovery_errors = vec![];\n        for step in ready\n            .iter()\n            .rev()","sourceCodeStart":658,"sourceCodeEnd":694,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/history/sync/apply.rs#L658-L694","documentation":"During `mise dot sync` pull, apply_locked_with_scope re-reads the shadow-history heads after writing directories and verifies they still match the commit that was planned (application_head and the upstream commit). If another process (user edit, concurrent sync, GC) moved the local or remote head between planning and finalization, mise aborts with this error rather than applying a plan computed against stale history. It is a deliberate optimistic-concurrency check protecting against applying a pull onto history that has been rewritten underneath it.","triggerScenarios":"Another `mise dot sync` (pull or push) or history-writing process commits to the shadow repo while a pull is being applied, so Heads::read at apply.rs:676 returns heads.local != application_head or heads.remote != status.upstream_commit. Also caused by external mutation of the shadow history store (manual git commands in the history repo, history GC/pruning) mid-pull.","commonSituations":"Running `mise dot sync` in two terminals at once; an editor/daemon that touches dotfiles while a slow pull is in flight; running history maintenance (prune/rebase of the history repo) concurrently with a pull.","solutions":["Simply re-run `mise dot sync` (pull) — the message explicitly says retry; the new plan will be computed against the current heads.","Ensure only one sync runs at a time (serialize syncs, or rely on mise's lock if available) so concurrent history writes stop racing.","Avoid manually mutating the shadow history repo (git reset/prune/rebase) while a sync is active.","If it recurs without concurrency, file a bug; it may indicate two code paths committing history without coordination."],"exampleFix":"// before: two terminals racing\n# terminal A: mise dot sync &\n# terminal B: mise dot sync   # -> setup history changed during adoption; retry pull\n// after\n# terminal A: mise dot sync\n# terminal B: wait for A, then: mise dot sync","handlingStrategy":"retry","validationCode":"// before pulling, check no other sync is running\npgrep -f 'mise dot sync' && echo 'another sync in progress — wait' || mise dot sync","typeGuard":null,"tryCatchPattern":"// retry with backoff on the specific message\nfor attempt in 1..=3 {\n  match run_sync() {\n    Ok(()) => break,\n    Err(e) if e.contains(\"setup history changed during adoption\") => sleep(backoff(attempt)),\n    Err(e) => return Err(e),\n  }\n}","preventionTips":["Run only one `mise dot sync` at a time.","Never hand-edit or git-mutate the shadow history repo during a sync.","Schedule syncs away from dotfile-watching daemons that commit history."],"tags":["git","concurrency","sync","retry"],"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"}