jdx/mise · error

changed during application; left untouched

Error message

{} changed during application; left untouched

What it means

After writing a directory permission step and committing the journal, verify_written re-observes the directory and requires it to match the `written` snapshot recorded during application. If it doesn't match, something changed the directory while (or right after) mise was applying it, so mise reports the failure and leaves the path untouched rather than claiming success. This is the post-write counterpart of the planning-time validation check.

Solutions

  1. Re-run `mise dot sync`; a new plan will apply permissions against the current state.
  2. Pause concurrent mutators (file-sync daemons, dotfile watchers) while running sync.
  3. Check who changed the mode: compare `stat` output before/after and look for daemons touching the directory.
Defensive patterns

Strategy: retry

Validate before calling

before=$(stat -c '%d:%i:%a' ~/.config/foo); mise dot sync >/dev/null; after=$(stat -c '%d:%i:%a' ~/.config/foo); [ "$before" = "$after" ] || echo 'external mutator detected'

Try / catch

try_sync_with_retry() { mise dot sync || { sleep 2; mise dot sync; } }

Prevention

When it happens

Trigger: `Step::verify_written` (called from `recover` / the apply finalization loop) sees observe(path) != step.written — the directory's dev/ino/mode changed after the permission write, e.g. a concurrent chmod, or the directory was replaced between the write and the verification read.

Common situations: A script or editor daemon chmods dotfile directories during sync; two machines/processes sync against the same working tree; a watcher (direnv, file sync service like Dropbox) mutates modes mid-apply.

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@533346cc37 (2026-09-17). Data as JSON: /api/errors/48dbf7fe7611e442. Report an issue: GitHub.

Appendix: source

Thrown at src/system/history/sync/directories.rs:129

    pub(super) fn apply(&mut self) -> Result<()> {
        self.validate()?;
        let pending = journal::begin_changes_with(
            "history",
            "directory permissions",
            [(self.path.clone(), journal::Capture::Shallow)],
        )?;
        if self.before.is_none() {
            crate::file::create_dir_all(&self.path)?;
        }
        set_mode(&self.path, self.desired)?;
        self.written = observe(&self.path)?;
        journal::commit_changes(pending);
        Ok(())
    }

    pub(super) fn verify_written(&self) -> Result<()> {
        if observe(&self.path)? != self.written {
            bail!(
                "{} changed during application; left untouched",
                crate::file::display_path(&self.path)
            );
        }
        Ok(())
    }

    pub(super) fn recover(&self) -> Result<()> {
        if self.written.is_none() {
            return Ok(());
        }
        self.verify_written()?;
        match self.before {
            Some((_, _, mode)) => set_mode(&self.path, mode),
            None => std::fs::remove_dir(&self.path).map_err(Into::into),
        }
    }
}

View on GitHub (pinned to 533346cc37)