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

  1. Review the printed plan, resolve the conflicts manually (remove/rename the changed paths), then re-run rollback/undo
  2. Re-run with `--force` to replace paths whose type changed: `mise dot rollback --to <ref> --all --force`
  3. 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

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


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)