jdx/mise · error

changed after the operation; left untouched

Error message

changed after the operation; left untouched

What it means

recover_path compares PathState::observe(path) against the recorded 'after' state before restoring; if they differ, the file was modified after the operation completed and recovery refuses to overwrite those newer edits, leaving the file untouched.

Source

Thrown at src/system/history/recovery.rs:86

    after: Option<&PathState>,
) -> Result<()> {
    validate_destination(path)?;
    let comparison = tempfile::tempdir()?;
    let depth = if matches!(prior, PathSnapshot::Directory { .. }) {
        Capture::Shallow
    } else {
        Capture::Full
    };
    // Comparing must not leave copies of concurrent user edits in the store.
    let current = PathSnapshot::capture_with(comparison.path(), path, depth);
    if !matches!(prior, PathSnapshot::Unrecorded { .. }) && &current == prior {
        return Ok(());
    }
    let Some(after) = after else {
        bail!("write completion was not recorded; inspect the live file before retrying recovery");
    };
    if PathState::observe(path) != *after {
        bail!("changed after the operation; left untouched");
    }
    // Entry count alone cannot establish a directory's identity. Never
    // replace a populated directory on that evidence.
    if matches!(after, PathState::Dir { entries, .. } if *entries != 0)
        && !(matches!(prior, PathSnapshot::Directory { .. })
            && matches!(
                after,
                PathState::Dir {
                    identity: Some(_),
                    ..
                }
            ))
    {
        bail!("directory contents cannot be verified safely; left untouched");
    }
    validate_snapshot(state_dir, prior)?;
    if PathState::observe(path) != *after {
        bail!("changed while preparing recovery; left untouched");

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Decide which version to keep: if the current edits are wanted, use `recover <operation> --keep-current`.
  2. If the recorded 'after' content is wanted, revert the live file to it manually, then re-run recovery.
  3. Stop editors/sync tools touching the file, then immediately retry `mise bootstrap dotfiles recover`.
  4. Diff the live file against the retained recovery data to choose the correct content.

Example fix

// before: file was edited after the operation, recover refuses
$ mise bootstrap dotfiles recover
// after: explicitly accept the newer edits
$ mise bootstrap dotfiles recover <operation> --keep-current
Defensive patterns

Strategy: try-catch

Try / catch

match recover_entries(state_dir, plan) {
    Err(e) if e.to_string().contains("changed after the operation") => {
        eprintln!("live file was edited; diff it, then choose --keep-current or restore");
    }
    other => other?,
}

Prevention

When it happens

Trigger: recover_path invoked when the live path's observed state (contents/mode) no longer equals the 'after' snapshot captured at operation completion — i.e. something edited the file between the operation and recovery.

Common situations: The user (or an editor/sync tool like Dropbox) changed the dotfile after the failed operation but before running recover; concurrent shells writing the same rc file; recovery run long after the interruption.

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


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/50555f330459bf9d. Report an issue: GitHub.