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 executing a restore, execute() computes a plan and detects conflicts: paths whose current on-disk state diverges from what the restore expects (including type changes like file→symlink). If any conflicts exist and the request is not forced, mise aborts with the conflict count, a pointer to the printed plan, and — when a conflicting path's type changed and --force was not given — a hint to pass --force.
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 afd2eddd3a)
Solutions
- Review the printed plan, resolve the listed conflicting paths manually, then re-run
- Re-run with --force to replace paths whose type changed: `... --force`
- Commit or stash local edits so the restore can proceed cleanly, then rollback/undo
Example fix
// before mise bootstrap dotfiles undo # 3 path(s) conflict, type changed // after mise bootstrap dotfiles undo --force # accept replacement of type-changed paths
Defensive patterns
Strategy: try-catch
Validate before calling
const plan = await previewPlan(req);
if (plan.conflicts.length > 0) {
console.warn(plan.conflicts.join("\n"));
if (!force) throw new Error("conflicts present; pass --force");
} Type guard
const isClean = (plan: Plan): plan is Plan & { conflicts: [] } =>
plan.conflicts.length === 0; Try / catch
try {
await undo();
} catch (e) {
if (String(e).includes("path(s) conflict")) {
if (typeChangedConfirmed) {
await undo({ force: true });
} else {
console.error("Resolve conflicts shown in the plan first.");
}
} else throw e;
} Prevention
- Preview the plan/diff before rolling back or undoing
- Commit or stash local dotfiles edits before restoring
- Watch for symlink↔file type changes from other config managers
- Use --force deliberately, only after reviewing the plan
When it happens
Trigger: execute (via rollback or undo) finds conflicts.len() > 0 and exec.force is false; the extra --force hint appears when type_changes is true. Typical causes: files edited locally after the checkpoint, symlink↔file swaps, different ownership/targets.
Common situations: User edited dotfiles since the checkpoint and now undoes; a tool replaced a symlink with a regular file (or vice versa); config managers (stow, chezmoi) changed link types.
Related errors
- brew-cask:{}: Homebrew owns this cask; remove it with Homebr
- brew-cask:{}: conflicts with installed cask {}
- brew-cask: cannot adopt '{}': existing artifact is not ident
- brew-cask: structured symlink target '{}' already exists
- brew-cask: completion target '{}' already exists and is not
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/f3150262d0aad257.
Report an issue: GitHub.