jdx/mise · error
path(s) conflict (see the plan above); resolve them first
Error message
{} path(s) conflict (see the plan above); resolve them first{hint} What it means
Before applying a restore plan, execute checks for conflicts (e.g. a tracked symlink now exists as a real directory or file — the path's type changed). If any conflicts remain and `--force` was not passed, the whole operation aborts so the user can review the printed plan first.
Solutions
- Review the printed plan, resolve the conflicts manually (remove/rename the changed paths), then re-run rollback/undo
- Re-run with `--force` to replace paths whose type changed: `mise dot rollback --to <ref> --all --force`
- Back up the conflicting path first if it may contain data, then force or delete it
Example fix
// before mise dot rollback --to checkpoint-42 --all # error: 1 path(s) conflict // after mise dot rollback --to checkpoint-42 --all --force
Defensive patterns
Strategy: validation
Validate before calling
const plan = await mise.dot.plan(req); // hypothetical dry-run
if (plan.conflicts.length > 0 && !req.force) {
throw new Error('resolve conflicts or pass --force');
} Try / catch
try {
await mise.dot.rollback(req);
} catch (e) {
if (/path\(s\) conflict/.test(e.message) && !req.force) {
req.force = true; // after reviewing/backing up
}
} Prevention
- Back up changed-type paths before forcing
- Check that tracked symlinks haven't been replaced by real dirs before rollback
- Use --force deliberately, never by default in scripts
When it happens
Trigger: `execute` (rollback/undo) finds `conflicts` non-empty while `exec.force` is false and the conflict is a changed path type — e.g. the tracked entry was a symlink but the live path is now a directory.
Common situations: A tool replaced a dotfile symlink with a real directory (or vice versa) after tracking; reinstalling an app that overwrites managed dotfiles; running rollback without `--force` after manual restructuring of config directories.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- changed after the operation; left untouched
- changed while preparing enrollment; concurrent declaration…
- changed while preparing recovery; left untouched
- choose only one resolution for
- conflicting dotfile declarations for
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/f3150262d0aad257.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/history/replay.rs:420
return Ok(());
}
let conflicts: Vec<&Step> = steps
.iter()
.filter(|step| matches!(step.action, Action::Conflict(_)))
.collect();
if !conflicts.is_empty() {
// `--force` answers a type change only (and undo already forces);
// an occupant history never captured stays a conflict whatever is
// passed, so it is not offered where it would not help
let type_changes = conflicts.iter().any(
|step| matches!(&step.action, Action::Conflict(reason) if reason.contains(" became ")),
);
let hint = if !exec.force && type_changes {
"; pass --force to replace a path whose type changed"
} else {
""
};
bail!(
"{} path(s) conflict (see the plan above); resolve them first{hint}",
conflicts.len()
);
}
// a link to a directory is not the directory: it counts as work left
let restores = exec
.restore_dirs
.iter()
.filter(|dir| std::fs::symlink_metadata(dir).is_err())
.count();
if !steps.iter().any(|step| step.action.mutates()) && restores == 0 {
info!("history: nothing to do");
return Ok(());
}
if !exec.yes && !prompt::confirm("history: apply this plan?")?.is_yes() {
info!("history: skipped");
return Ok(());
}View on GitHub (pinned to 533346cc37)