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
- Simply re-run the `mise dot track` command — the concurrent edit was preserved and is safe
- Serialize the enrollments (run them one at a time or under your own lock in scripts)
- 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
- Don't run multiple `mise dot track`/`untrack` commands in parallel
- Don't hand-edit the declaration file while track commands run
- Suspend dotfile-sync daemons during enrollment
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
- changed after permission planning; retry pull
- changed after the operation; left untouched
- changed while the changes were being applied; nothing more…
- choose only one resolution for
- conflicting dotfile declarations for
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)