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

  1. Re-run `mise dot sync` as the message advises — a fresh plan is computed from current state.
  2. Ensure no other process (package manager, dotfile installer, second sync) recreates or chmods the directory during a pull.
  3. 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

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


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)