jdx/mise · error
target changed after unapply planning
Error message
target changed after unapply planning
What it means
During unapply, mise first snapshots each target file's text (the plan), then validate_unapply re-reads the file and compares it to the planned text. If the file's content changed between planning and validation — including switching to/from a symlink — mise aborts rather than unapply against stale assumptions.
Source
Thrown at src/system/edits.rs:968
}
/// Ensure template functions or another concurrent actor did not invalidate
/// any edit ownership checks performed during planning.
pub(crate) fn validate_unapply(todo: &[UnapplyPlan<'_>]) -> Result<()> {
let mut checked = indexmap::IndexSet::new();
let mut problems = vec![];
for plan in todo {
if !checked.insert(plan.req.path.clone()) {
continue;
}
let result = if plan.req.path.is_symlink() {
Err(eyre::eyre!("{SYMLINK_REASON}"))
} else {
file::read_to_string(&plan.req.path).and_then(|text| {
if text == plan.text {
Ok(())
} else {
bail!("target changed after unapply planning")
}
})
};
if let Err(err) = result {
problems.push(format!(
" \"{}\" ({}): {err}",
plan.req.path_raw,
plan.req.describe_op()
));
}
}
if !problems.is_empty() {
bail!(
"edits: cannot unapply these entries:\n{}",
problems.join("\n")
);
}
Ok(())View on GitHub (pinned to afd2eddd3a)
Solutions
- Re-run the unapply command — a fresh plan will pick up the new file contents.
- Find what modified the file concurrently (editor autosave, sync daemon, watcher) and pause it.
- If the file flipped to a symlink, edit the real file manually instead.
Example fix
# before: file changed mid-run $ mise edits unapply error: target changed after unapply planning # after: retry when nothing else touches the files $ mise edits unapply # fresh plan succeeds
Defensive patterns
Strategy: retry
Validate before calling
# snapshot and compare to detect concurrent writers md5sum ~/.zshrc; mise edits unapply; md5sum ~/.zshrc
Try / catch
// retry the whole unapply once with a fresh plan
match run_unapply().await {
Err(e) if e.to_string().contains("target changed after unapply planning") => retry_with_fresh_plan().await,
other => other,
} Prevention
- Close editors/formatters/ watchers that rewrite dotfiles during mise commands
- Pause sync daemons (Dropbox etc.) while unapplying
- Re-run the command — each run rebuilds the plan from current contents
When it happens
Trigger: Calling validate_unapply when file::read_to_string(plan.req.path) returns text different from plan.text, i.e. the file was modified after plan_unapply ran (or the path became a symlink, which is rejected separately with SYMLINK_REASON).
Common situations: An editor, formatter, file watcher, or another mise/command run rewrote the dotfile between planning and execution of an unapply; or a sync process (e.g. Dropbox, a dotfile manager) touched the file mid-run.
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
- edits: cannot unapply these entries: {}
- edits: "{}": {reason}, fix the file manually
- staged blob does not match the declared CAS digest
- staged blob size does not match the declared CAS digest
- files: cannot unapply these entries:\n{}
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/e1fd6ce9b9a95d10.
Report an issue: GitHub.