jdx/mise · error

choose only one resolution for

Error message

choose only one resolution for {}

What it means

During a dotfile sync apply, conflicts can be resolved with a blanket choice (take remote or keep local) plus per-path choices. If the same path appears in both the take-remote and keep-local sets, the resolution is contradictory and mise fails with this error naming the file. You must pick exactly one resolution per path.

Solutions

  1. Remove the duplicated path from one of the two resolution groups.
  2. Decide per path: either take the remote version or keep the local one, not both.
  3. If the lists are generated, deduplicate and validate that the two sets are disjoint before invoking.
  4. Re-run the apply after correcting the resolution sets.

Example fix

// before
mise dotfiles pull --take-remote ~/.gitconfig --keep-local ~/.gitconfig
Error: choose only one resolution for ~/.gitconfig

// after
mise dotfiles pull --take-remote ~/.gitconfig
Defensive patterns

Strategy: validation

Validate before calling

let dupes: HashSet<_> = take_remote.intersection(&keep_local).collect();
if !dupes.is_empty() {
  eprintln!("conflicting resolutions for: {:?}", dupes);
}

Type guard

fn resolutions_are_disjoint(take_remote: &HashSet<PathBuf>, keep_local: &HashSet<PathBuf>) -> bool {
  take_remote.is_disjoint(keep_local)
}

Prevention

When it happens

Trigger: Invoking apply/pull where conflict-resolution flags/options put the same path into both `take_remote` and `keep_local` — e.g. passing the same path to both a '--theirs' style list and a '--ours' style list in one command.

Common situations: Command lines like `mise dotfiles pull --take ~/.gitconfig --keep ~/.gitconfig`; generated commands where duplicate paths land in both option groups; copy-paste errors in long path lists.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/af2a777318023f56. Report an issue: GitHub.

Appendix: source

Thrown at src/system/history/sync/apply.rs:199

        };
        blanket.extend(blanket_chosen.iter().cloned());
    }

    // Store choices without publishing or applying any part of the setup.
    let mut sync_state = state::load(repo)?;
    let shared = super::share::current(repo, tracked)?.objects();
    let encrypted = super::files::encrypted_paths(repo, status.upstream_commit.as_deref())?;
    for conflict in &status.conflicts {
        let Some(local) = roots
            .locate(&conflict.branch_path)
            .path()
            .map(Path::to_path_buf)
        else {
            continue;
        };
        if take_remote.contains(&local) || keep_local.contains(&local) {
            if take_remote.contains(&local) && keep_local.contains(&local) {
                bail!("choose only one resolution for {}", display_path(&local));
            }
            // A blanket choice cannot speak for a path whose live side is not
            // a file or symlink, and failing here would discard every other
            // decision this pass makes. Hold that one path and keep going; a
            // path named on the command line still reports the failure. The
            // check belongs on this read, so a path that changed since the
            // blanket choice was made is held too rather than aborting it.
            // Only the path's own fault is held: a repository failure on an
            // otherwise sound file is nobody's to fix by hand, so it stops
            // the pass with its own error.
            let live = match live_object(repo, &local) {
                Ok(live) => live,
                Err(err)
                    if blanket_chosen.contains(&local)
                        && err.downcast_ref::<UnusableLive>().is_some() =>
                {
                    blanket_held.push(err.to_string());
                    take_remote.remove(&local);

View on GitHub (pinned to 533346cc37)