{"record":{"id":"48dbf7fe7611e442","repo":"jdx/mise","slug":"changed-during-application-left-untouched","errorCode":null,"errorMessage":"{} changed during application; left untouched","messagePattern":"(.+?) changed during application; left untouched","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/sync/directories.rs","lineNumber":129,"sourceCode":"    pub(super) fn apply(&mut self) -> Result<()> {\n        self.validate()?;\n        let pending = journal::begin_changes_with(\n            \"history\",\n            \"directory permissions\",\n            [(self.path.clone(), journal::Capture::Shallow)],\n        )?;\n        if self.before.is_none() {\n            crate::file::create_dir_all(&self.path)?;\n        }\n        set_mode(&self.path, self.desired)?;\n        self.written = observe(&self.path)?;\n        journal::commit_changes(pending);\n        Ok(())\n    }\n\n    pub(super) fn verify_written(&self) -> Result<()> {\n        if observe(&self.path)? != self.written {\n            bail!(\n                \"{} changed during application; left untouched\",\n                crate::file::display_path(&self.path)\n            );\n        }\n        Ok(())\n    }\n\n    pub(super) fn recover(&self) -> Result<()> {\n        if self.written.is_none() {\n            return Ok(());\n        }\n        self.verify_written()?;\n        match self.before {\n            Some((_, _, mode)) => set_mode(&self.path, mode),\n            None => std::fs::remove_dir(&self.path).map_err(Into::into),\n        }\n    }\n}","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/history/sync/directories.rs#L111-L147","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","solutions":["Re-run `mise dot sync`; a new plan will apply permissions against the current state.","Pause concurrent mutators (file-sync daemons, dotfile watchers) while running sync.","Check who changed the mode: compare `stat` output before/after and look for daemons touching the directory."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"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'","typeGuard":null,"tryCatchPattern":"try_sync_with_retry() { mise dot sync || { sleep 2; mise dot sync; } }","preventionTips":["Disable file-sync daemons (Dropbox/Syncthing) or mode-changing watchers on synced trees during pulls.","Avoid concurrent chmod automation on the same directories.","Investigate any daemon that resets permissions (they often fight sync)."],"tags":["concurrency","permissions","sync","filesystem"],"backgroundTag":"invalid-state-transition","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}