{"record":{"id":"d6b180ba270ea19d","repo":"jdx/mise","slug":"changed-during-recovery-left-untouched","errorCode":null,"errorMessage":"{} changed during recovery; left untouched","messagePattern":"(.+?) changed during recovery; left untouched","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/sync/apply.rs","lineNumber":666,"sourceCode":"    }\n    if !req.automatic {\n        info!(\"history: applied {written} incoming change(s)\");\n    }\n    let outcome = ApplyOutcome {\n        written,\n        held: held.len(),\n        configuration,\n    };\n    Ok(outcome)\n}\n\nfn recover_step(repo: &crate::system::history::shadow::HistoryRepo, step: &Step) -> Result<()> {\n    let current = live_object(repo, &step.path)?;\n    if current == step.before && live_permissions(&step.path)? == step.before_mode {\n        return Ok(());\n    }\n    if current != step.pending.object || live_permissions(&step.path)? != step.desired_mode {\n        bail!(\n            \"{} changed during recovery; left untouched\",\n            display_path(&step.path)\n        );\n    }\n    match &step.before {\n        Some((mode, oid)) => {\n            #[cfg(unix)]\n            let bits = {\n                use std::os::unix::fs::PermissionsExt;\n                step.permissions.as_ref().map(|p| p.mode() & 0o777)\n            };\n            #[cfg(not(unix))]\n            let bits = None;\n            replay::write_path_with_mode(repo, &step.path, mode, oid, bits)?;\n        }\n        None => replay::remove(&step.path)?,\n    }\n    if !step.path.is_symlink()","sourceCodeStart":648,"sourceCodeEnd":684,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/sync/apply.rs#L648-L684","documentation":"recover_step restores a file's pre-image after a partially applied sync step. Before restoring it checks the live content still equals the pending 'object' and the permissions equal the desired mode; if the file was modified concurrently, recovery refuses to overwrite it and leaves the file untouched.","triggerScenarios":"Calling apply (or recovery) while the target file is being edited; the live file's bytes or mode differ from step.pending.object / step.desired_mode at recovery time.","commonSituations":"An editor or another process saved the file between apply and recovery; a partially applied pull left the file in a state a running app modified; permissions changed via chmod.","solutions":["Review the file's current contents, then manually restore or discard changes and re-run the pull.","Stop the process holding/writing the file and retry recovery.","Commit or back up the concurrent edit before re-running sync."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"let current = live_object(repo, &step.path)?;\nif current == step.pending.object {\n    // safe to recover\n}","typeGuard":null,"tryCatchPattern":"match apply_result {\n    Err(e) if e.to_string().contains(\"changed during recovery\") => {\n        // inspect and manually reconcile the file, then retry\n    }\n    other => other?,\n}","preventionTips":["Close editors and stop processes holding tracked files during pulls","Save or commit concurrent edits before running recovery","Avoid mixing manual edits with in-flight sync operations"],"tags":["filesystem","concurrency","recovery","sync"],"backgroundTag":"file-write-failed","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"}