jdx/mise · error

{} changed after permission planning; retry pull

Error message

{} changed after permission planning; retry pull

What it means

validate() re-observes a directory right before applying a planned permission step and compares against the 'before' snapshot taken during planning. Any change (identity or mode) between plan and apply aborts with a retry instruction, preventing permissions from being applied to a swapped directory.

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 afd2eddd3a)

Solutions

  1. Re-run the pull; it re-plans against the current directory state.
  2. Stop whatever process is mutating the directory concurrently, then retry.
  3. Verify the directory's dev/ino/mode stability (nothing recreating it) before retrying.
Defensive patterns

Strategy: retry

Validate before calling

let before = observe(&path)?; // compare immediately before apply
if before != planned_before { /* re-plan */ }

Try / catch

if let Err(e) = apply(&plan) {
    if e.to_string().ends_with("changed after permission planning; retry pull") {
        // simply retry the pull
    }
}

Prevention

When it happens

Trigger: Directory deleted/recreated, replaced by a file/symlink, or chmodded between plan() and validate() inside apply.

Common situations: Another process (installer, sync tool) touching the directory mid-pull; two mise pulls racing; user editing permissions while a pull runs.

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/6eb8602a9a4b0d51. Report an issue: GitHub.