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
- Re-run the pull; it re-plans against the current directory state.
- Stop whatever process is mutating the directory concurrently, then retry.
- 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
- Don't touch/chmod tracked directories while a pull runs
- Run pulls serially, not concurrently
- Disable agents that recreate directories mid-operation
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
- {} changed during application; left untouched
- changed while preparing recovery; left untouched
- {} changed before application; nothing was written
- {} changed while the changes were being applied; nothing mor
- {} changed during setup adoption; retry pull
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/6eb8602a9a4b0d51.
Report an issue: GitHub.