jdx/mise · error
changed after permission planning; retry pull
Error message
{} changed after permission planning; retry pull What it means
After planning directory permission steps but before applying them, Step::validate re-observes the directory (dev, ino, mode) and requires it to equal the `before` snapshot captured during planning. If anything about the directory changed in between — replaced, recreated, chmod'ed — mise aborts with this retryable error rather than writing permissions derived from a stale snapshot. The plan was computed against state that no longer exists.
Solutions
- Re-run `mise dot sync` as the message advises — a fresh plan is computed from current state.
- Ensure no other process (package manager, dotfile installer, second sync) recreates or chmods the directory during a pull.
- After a recreate, verify the directory's inode/permissions are what you intend before re-syncing.
Example fix
// before $ mkdir -p ~/.config/foo && mise dot sync & $ rm -rf ~/.config/foo && mkdir ~/.config/foo # invalidates plan // after $ mise dot sync # retry after any directory recreation completes
Defensive patterns
Strategy: retry
Validate before calling
# capture before/after inode+mode to detect mid-sync changes before=$(stat -c '%d:%i:%a' ~/.config/foo); mise dot sync; after=$(stat -c '%d:%i:%a' ~/.config/foo); [ "$before" = "$after" ] || echo 'directory changed during sync — retry'
Try / catch
for i in 1 2 3; do mise dot sync && break; sleep 2; done
Prevention
- Don't recreate or chmod synced directories while a pull runs.
- Finish installers/declarative dotfile managers before syncing.
- Run syncs when no watchers are mutating the tree.
When it happens
Trigger: `Step::validate` (called from `apply`) observes a (dev, ino, mode) triple that differs from step.before — the directory was deleted and recreated (new inode), chmod'ed, or swapped with another directory between plan() and apply().
Common situations: A dotfiles manager or install script recreated the directory while the pull was running; two syncs raced; a chmod ran concurrently with the pull.
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
- changed while the changes were being applied; nothing more…
- setup history changed during adoption; retry pull
- another setup sync or pull is running; retry shortly
- cannot check staged changes in
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/6eb8602a9a4b0d51.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/history/sync/directories.rs:103
);
}
if before.map(|(_, _, bits)| bits) != Some(desired) {
steps.push(Step {
path,
before,
desired,
written: None,
});
}
}
steps.sort_by_key(|step| step.path.components().count());
Ok(steps)
}
impl Step {
pub(super) fn validate(&self) -> Result<()> {
if observe(&self.path)? != self.before {
bail!(
"{} changed after permission planning; retry pull",
crate::file::display_path(&self.path)
);
}
Ok(())
}
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)?;View on GitHub (pinned to 533346cc37)