jdx/mise · error

save {} before choosing --keep-local

Error message

save {} before choosing --keep-local

What it means

Resolving a conflict with --keep-local publishes the currently live file content as the resolution, so the on-disk file must exactly match the version that was last saved into the shared history store. If the live object or its permissions differ from the saved copy, mise refuses to silently pick up unsaved edits and asks you to save (re-sync) the file first. Nothing is written when this error fires.

Source

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

        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));
            }
            let live = live_object(repo, &local)?;
            let saved = shared.get(&conflict.branch_path).cloned();
            let saved_mode = permissions_at(
                repo,
                planned_head.as_deref(),
                &conflict.branch_path,
                saved.as_ref(),
            )?;
            if keep_local.contains(&local)
                && (live != saved || live_permissions(&local)? != saved_mode)
            {
                bail!("save {} before choosing --keep-local", display_path(&local));
            }
            let remote = match status.upstream_commit.as_deref() {
                Some(head) => repo
                    .object_at(head, &conflict.branch_path)?
                    .map(|object| {
                        if !encrypted.contains(&conflict.branch_path) {
                            return Ok(object);
                        }
                        super::files::decrypt(
                            repo,
                            &conflict.branch_path,
                            &object,
                            !req.automatic && console::user_attended_stderr(),
                        )
                    })
                    .transpose()?,
                None => None,
            };

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Save the current file into the sync store first (run a save/push sync step, or the save command of the history system), then re-run pull with --keep-local.
  2. If the local edits should be discarded, resolve with --take-remote for this path instead of --keep-local.
  3. Compare live vs saved state with a dry-run pull to see which paths are affected, restore expected permissions (chmod) if only the mode differs, then retry.

Example fix

# before (file edited after last save)
mise bootstrap dotfiles pull --keep-local ~/.zshrc
# after: save the current state first, then resolve
mise bootstrap dotfiles push ~/.zshrc
mise bootstrap dotfiles pull --keep-local ~/.zshrc
Defensive patterns

Strategy: validation

Validate before calling

# shell: ensure no unsaved drift before --keep-local
mise bootstrap dotfiles pull --dry-run || true
# compare live file with last saved state, e.g. via the status output, then save first
mise bootstrap dotfiles push
mise bootstrap dotfiles pull --keep-local "$path"

Try / catch

try {
  await pull({ keepLocal: [path] });
} catch (e) {
  if (/save .* before choosing --keep-local/.test(e.message)) {
    await push();            // save live state into the sync store
    await pull({ keepLocal: [path] });
  } else throw e;
}

Prevention

When it happens

Trigger: Running `mise bootstrap dotfiles pull --keep-local <path>` while the live file content or permissions differ from `shared.get(branch_path)` (the object recorded in the shared history store), i.e. the file was edited after the last successful sync/save.

Common situations: Editing a dotfile in an editor and immediately trying to resolve the conflict without saving it into the history; permission bits changed via chmod after enrollment; a previous pull/push failed midway leaving the saved copy stale; another tool rewrote the file between syncs.

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


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/26f0758ec36a9b8d. Report an issue: GitHub.