jdx/mise · error

repos: {} has local changes; commit, stash, or clean them be

Error message

repos: {} has local changes; commit, stash, or clean them before bootstrap

What it means

Before applying any bootstrap changes, preflight_statuses inspects every configured repo; RepoState::Dirty is assigned when `git status --porcelain=v1` in that checkout returns any output — modified, staged, or untracked files all count. mise refuses to check out or pull in a dirty worktree to avoid destroying local changes, and this bail names the offending repo (path displayed home-tildefied) before anything is mutated.

Source

Thrown at src/system/repos.rs:161

        })
    }
}

impl std::fmt::Display for RepoRequest {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", file::display_path(&self.path))
    }
}

pub fn status(requests: &[RepoRequest]) -> Result<Vec<RepoStatus>> {
    requests.iter().map(status_one).collect()
}

pub fn preflight_statuses(statuses: &[RepoStatus]) -> Result<()> {
    for status in statuses {
        match &status.state {
            RepoState::Dirty => {
                bail!(
                    "repos: {} has local changes; commit, stash, or clean them before bootstrap",
                    status.request
                );
            }
            RepoState::Conflict(reason) => {
                bail!("repos: {}: {reason}", status.request);
            }
            RepoState::Current | RepoState::Missing | RepoState::Differs => {}
        }
    }
    Ok(())
}

/// Apply statuses previously validated with [`preflight_statuses`].
pub(crate) fn apply_statuses(statuses: &[RepoStatus], dry_run: bool) -> Result<()> {
    for status in statuses {
        match &status.state {
            RepoState::Current => {

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Commit or stash the changes in the named repo — `git -C <repo> stash -u` also covers untracked files
  2. Add untracked noise to the repo's .gitignore and commit that change
  3. Remove the entry from `[bootstrap.repos]` if you manage that clone manually
  4. Run `git -C <repo> status --porcelain` yourself to see exactly which lines make it dirty

Example fix

# before: mise bootstrap fails with "has local changes"
$ git -C ~/src/dotfiles status --porcelain
?? local.txt
 M README.md

# after
$ git -C ~/src/dotfiles stash -u
$ mise bootstrap
Defensive patterns

Strategy: validation

Validate before calling

fn repo_is_clean(path: &Path) -> bool {
    std::process::Command::new("git")
        .arg("-C").arg(path)
        .args(["status", "--porcelain=v1"])
        .output()
        .map(|o| o.status.success() && o.stdout.is_empty())
        .unwrap_or(false)
}

// before `mise bootstrap`:
assert!(repos.iter().all(|r| repo_is_clean(&r.path)), "worktree not clean");

Prevention

When it happens

Trigger: Running `mise bootstrap` (or `mise system install` flows that apply repos) while any `[bootstrap.repos]` checkout has uncommitted modifications, staged changes, or untracked files.

Common situations: Local edits in a dotfiles repo; generated files (build artifacts, `.env`, editor swap files) that are not gitignored — untracked files count as dirty; two bootstrap runs racing.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/48386a4a220c2020. Report an issue: GitHub.