jdx/mise · error
lockfile symlink changed during preparation; retry the…
Error message
lockfile symlink {} changed during preparation; retry the command What it means
mise's lockfile generation resolves tools against the target of the `source_symlink` (e.g. a versioned lockfile symlink pointing at the active lockfile deployment). Before publishing sidecar files it rechecks that the symlink still points to the same canonical target it saw when preparation started. If the symlink was removed, replaced, or re-pointed mid-run, it aborts before publishing anything so a stale or foreign lockfile is not augmented, and asks the user to rerun.
Solutions
- Rerun the exact same mise command; the check is intentionally retry-safe.
- Ensure only one mise process mutates the lockfile at a time (serialize installs and `mise lock` invocations in scripts/CI).
- If a migration or upgrade switched the symlink deliberately, run the new command against the current deployment instead of the interrupted one.
Example fix
// before: concurrent commands race on the symlink mise install & mise lock --upgrade & wait // after: serialize lockfile-mutating commands mise lock --upgrade && mise install
Defensive patterns
Strategy: retry
Validate before calling
# before running mise, confirm the lockfile symlink is stable readlink mise.lock && readlink mise.lock # same target twice => stable
Prevention
- Never run concurrent mise commands that mutate lockfiles in the same project.
- Serialize `mise install` and `mise lock --upgrade` in scripts with && or a lock.
- Avoid manual `ln -sfn` on mise.lock while builds are running.
When it happens
Trigger: Calling any mise command that publishes lockfile sidecars (install/lock resolution with lockfile_enabled) while another process switches the source lockfile symlink between read and publish; concurrent `mise lock --upgrade` or monorepo migration in another terminal; an editor/CI job swapping the symlink target during a long install.
Common situations: Two concurrent mise commands (e.g. `mise install` and `mise lock --upgrade`) racing on the same project; a deployment script rotating the lockfile symlink while a build agent installs tools; manual `ln -sfn` edits during a running install.
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
- file changed during lockfile generation; retry the command
- lockfile changed while generation was being prepared; retry…
- changed after permission planning; retry pull
- changed while preparing enrollment; concurrent declaration…
- changed while the changes were being applied; nothing more…
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/527b39c7e83c89db.
Report an issue: GitHub.
Appendix: source
Thrown at src/lockfile.rs:1975
pub(crate) struct GraphCleanup(graph::SidecarWrites);
impl GraphCleanup {
pub(crate) fn prune(self) -> Result<()> {
self.0.prune()
}
}
impl PreparedWrite {
pub(crate) fn mutation_paths(&self) -> impl Iterator<Item = &PathBuf> {
self.sidecars.files.iter().map(|(path, _)| path)
}
pub(crate) fn publish_deferred(self) -> Result<GraphCleanup> {
// Detect a deployment switch during preparation before publishing any
// sidecars. This is a recheck, not a lock against external symlink edits.
if let Some(path) = &self.source_symlink
&& (!path.is_symlink()
|| !fs::canonicalize(path).is_ok_and(|target| target == self.target))
{
bail!(
"lockfile symlink {} changed during preparation; retry the command",
display_path(path)
);
}
self.sidecars.publish_files()?;
if let Some(tmp) = self.tmp {
persist_lockfile_tmp(tmp, &self.target)?;
}
invalidate_caches();
Ok(GraphCleanup(self.sidecars))
}
pub(crate) fn publish(self) -> Result<()> {
self.publish_deferred()?.prune()
}
}
/// Determines the lockfile path for a given config file path
/// Returns (lockfile_path, is_local)View on GitHub (pinned to 533346cc37)