jdx/mise · error
changed during recovery; left untouched
Error message
{} changed during recovery; left untouched What it means
During pull application, recover_step restores each path to its pre-image (`step.before`) when a step must be rolled back or reconciled. Before touching the file it re-reads the live object and permissions: if the live content no longer equals the planned pending object/mode, the world no longer matches what the recovery step was computed for, so mise refuses to touch the file and reports it was 'left untouched'. This prevents recovery from clobbering concurrent user edits.
Solutions
- Inspect the reported path — mise deliberately left it untouched; decide manually whether to keep your edit or let the next `mise dot sync` overwrite it.
- Re-run `mise dot sync` after resolving the file; a fresh plan will be computed from current state.
- If you want the pulled version, revert your local change (or stash it) and re-run the sync.
- Avoid editing dotfiles while a sync is in progress.
Defensive patterns
Strategy: validation
Validate before calling
// confirm the file is in the expected state before re-running sync current=$(sha256sum ~/.config/target | cut -d' ' -f1) echo "live hash: $current — if you edited it, resolve manually before syncing"
Prevention
- Don't edit dotfiles while a sync is running.
- After a failed/interrupted pull, check the reported path before re-running sync.
- Let mise resolve conflicts rather than editing files mid-operation.
When it happens
Trigger: Recovery (rollback or crash-recovery replay of a Step) runs after the file's live content or permissions changed from what the plan captured: `current != step.pending.object || live_permissions != step.desired_mode`. Typically another process modified the dotfile between planning and recovery.
Common situations: A user edits a dotfile while a pull is mid-flight and the pull must roll back; a crash leaves a stale journal and by the time recovery replays, the file was edited or replaced; two syncs race on the same file.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- changed during application; left untouched
- another setup sync or pull is running; retry shortly
- changed after it was protected; nothing more was changed
- changed after permission planning; retry pull
- changed while the changes were being applied; nothing more…
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/d6b180ba270ea19d.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/history/sync/apply.rs:796
repo: &crate::system::history::shadow::HistoryRepo,
head: &str,
) -> Result<()> {
if crate::config::Settings::get()
.history
.allow_plaintext_history
{
return Ok(());
}
super::files::audit_history(repo, head, &Default::default())
}
fn recover_step(repo: &crate::system::history::shadow::HistoryRepo, step: &Step) -> Result<()> {
let current = live_object(repo, &step.path)?;
if current == step.before && live_permissions(&step.path)? == step.before_mode {
return Ok(());
}
if current != step.pending.object || live_permissions(&step.path)? != step.desired_mode {
bail!(
"{} changed during recovery; left untouched",
display_path(&step.path)
);
}
match &step.before {
Some((mode, oid)) => {
#[cfg(unix)]
let bits = {
use std::os::unix::fs::PermissionsExt;
step.permissions.as_ref().map(|p| p.mode() & 0o777)
};
#[cfg(not(unix))]
let bits = None;
replay::write_path_with_mode(repo, &step.path, mode, oid, bits)?;
}
None => replay::remove(&step.path)?,
}
if !step.path.is_symlink()View on GitHub (pinned to 533346cc37)