{"record":{"id":"bb346950a4d2a806","repo":"jdx/mise","slug":"local-files-were-saved-after-enrollment-planning","errorCode":null,"errorMessage":"local files were saved after enrollment planning; retry pull","messagePattern":"local files were saved after enrollment planning; retry pull","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/sync/apply.rs","lineNumber":475,"sourceCode":"                .as_deref()\n                .ok_or_else(|| eyre::eyre!(\"local history disappeared\"))?;\n            // The operation's own protective commit may save the unsaved\n            // versions that an explicit --take-remote decision will replace.\n            // Permit only changes at those preflighted paths; any other saved\n            // change requires a fresh complete plan.\n            let restore_planned = ready\n                .iter()\n                .map(|step| {\n                    Ok(crate::system::history::shadow::Overlay {\n                        path: step.pending.branch_path.clone(),\n                        object: repo.object_at(local, &step.pending.branch_path)?,\n                    })\n                })\n                .collect::<Result<Vec<_>>>()?;\n            if repo.compose(&repo.output_tree_of(current)?, &restore_planned)?\n                != repo.output_tree_of(local)?\n            {\n                bail!(\"local files were saved after enrollment planning; retry pull\");\n            }\n            // Do not let a divergent text merge or a stale resolution differ\n            // from the complete tree that will be adopted.\n            for step in &ready {\n                if repo.restored_object_at(tree, &step.pending.branch_path)? != step.pending.object\n                {\n                    bail!(\n                        \"{} changed in the complete repository merge; reconcile again\",\n                        display_path(&step.path)\n                    );\n                }\n            }\n            let candidate = heads\n                .candidate(repo, tree)?\n                .ok_or_else(|| eyre::eyre!(\"setup branch disappeared\"))?;\n            super::files::audit_history(repo, &candidate.commit, &Default::default())?;\n        }\n        // Validate the complete batch again after acquiring the operation","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/sync/apply.rs#L457-L493","documentation":"Before adopting the enrollment/restore plan, mise composes the planned restore tree and compares it against the complete local tree. If they differ, local files were modified after enrollment planning computed the plan, so the plan is stale and the pull is aborted with this error, asking you to retry. Nothing is written; retrying recomputes a fresh plan.","triggerScenarios":"During pull, `repo.compose(output_tree_of(current), restore_planned) != output_tree_of(local)` — any live file under tracking changed between when the enrollment plan was built and when apply ran.","commonSituations":"An editor, dotfile manager, or package installer touched dotfiles between planning and apply; a concurrently running second `mise bootstrap dotfiles` command; long gaps between planning and applying on a machine with active config churn.","solutions":["Re-run `mise bootstrap dotfiles pull` to rebuild the plan against the current tree — this is the explicitly suggested action in the message.","Ensure no other process (editors, other mise instances, sync daemons) modifies tracked files during pull; run pull exclusively.","If changes were unintentional, restore the files to the planned state or re-enroll so the plan and tree converge."],"exampleFix":"// before: stale plan detected\nbail!(\"local files were saved after enrollment planning; retry pull\");\n// after: caller-side handling\nmatch pull().await {\n    Err(e) if e.to_string().contains(\"retry pull\") => pull().await, // rebuild plan\n    other => other,\n}","handlingStrategy":"retry","validationCode":"# ensure no other process is editing tracked dotfiles during pull\nfuser -v ~/.zshrc 2>/dev/null || true   # example: check for concurrent writers","typeGuard":null,"tryCatchPattern":"async function pullWithReplan() {\n  try { return await pull(); }\n  catch (e) {\n    if (/retry pull/.test(e.message)) return await pull(); // rebuild the plan once\n    throw e;\n  }\n}","preventionTips":["Run pull exclusively — close editors and stop dotfile daemons/watchers during it.","Apply pulls soon after planning; don't leave long gaps on machines with active config churn.","Re-run pull after any intentional local change instead of applying an old plan."],"tags":["sync","stale-plan","concurrency","retry","dotfiles"],"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-14T00:17:10.932Z"}