jdx/mise · error

changed while preparing enrollment; concurrent declaration…

Error message

{} changed while preparing enrollment; concurrent declaration edit preserved; retry

What it means

Before committing a declaration edit, track.rs re-reads the declaration file and verifies it still matches the content the edit was prepared against (compare-and-swap guard). If another process modified it concurrently, the bail fires to preserve that concurrent edit; the whole enrollment must be retried from scratch.

Solutions

  1. Simply re-run the `mise dot track` command — the concurrent edit was preserved and is safe
  2. Serialize the enrollments (run them one at a time or under your own lock in scripts)
  3. Inspect the declaration file if repeated retries keep failing, to find the concurrent writer
Defensive patterns

Strategy: retry

Try / catch

for i in 1 2 3; do mise dot track ~/.gitconfig && break; sleep 1; done

Prevention

When it happens

Trigger: Two `mise dot track` (or track/untrack) invocations overlapping in time: process A reads the declaration file under declaration_lock(), prepares its edit, and by the time it re-checks, process B (or any external editor) has rewritten the file.

Common situations: Parallel CI jobs enrolling dotfiles simultaneously; a user hand-editing the global declaration config while mise is tracking another path; sync tools (e.g. dotfile sync daemons) rewriting the declaration.

Related errors


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

Appendix: source

Thrown at src/cli/dotfiles/track.rs:332

    declaration_lock_for(&crate::config::global_shared_config_path())
}

fn declaration_lock_for(config: &Path) -> Result<fslock::LockFile> {
    crate::lock_file::LockFile::new(&config.with_extension("dotfiles-declarations.lock"))
        .try_lock()?
        .ok_or_else(|| {
            eyre::eyre!("another tracking declaration command is running; retry shortly")
        })
}

fn check_declaration(path: &Path, expected: Option<&str>) -> Result<()> {
    let current = match std::fs::read_to_string(path) {
        Ok(body) => Some(body),
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,
        Err(error) => return Err(error.into()),
    };
    if current.as_deref() != expected {
        bail!(
            "{} changed while preparing enrollment; concurrent declaration edit preserved; retry",
            display_path(path)
        );
    }
    Ok(())
}

fn commit_declaration(
    path: &Path,
    expected: Option<&str>,
    prepared: file::PreparedAtomicWrite,
) -> Result<()> {
    // Check after formatting, directory creation, writing, and fsync. The
    // declaration lock coordinates mise writers; unrelated editors do not
    // participate, so this is not a filesystem compare-and-swap guarantee.
    check_declaration(path, expected)?;
    prepared.commit()
}

View on GitHub (pinned to 533346cc37)