jdx/mise · error · eyre::Report
too many symlinks while resolving atomic write target: {}
Error message
too many symlinks while resolving atomic write target: {} What it means
Atomic writes in src/file.rs resolve the destination through symlinks before persisting a temp file, following at most `MAX_SYMLINKS` links. Hitting the limit means the chain is a symlink loop (a -> b -> a) or an absurdly long chain; writing through it is unsafe and would never terminate, so the write bails naming the original path.
Source
Thrown at src/file.rs:519
let mut target = path.to_path_buf();
for followed in 0..=MAX_SYMLINKS {
if !target.is_symlink() {
return Ok(desymlink_path(&target));
}
if followed == MAX_SYMLINKS {
break;
}
let link = fs::read_link(&target)
.wrap_err_with(|| format!("failed to read symlink: {}", display_path(&target)))?;
target = if link.is_absolute() {
link
} else {
target.parent().unwrap_or_else(|| Path::new("")).join(link)
};
}
bail!(
"too many symlinks while resolving atomic write target: {}",
display_path(path)
)
}
fn persist_atomic(mut temporary: tempfile::NamedTempFile, path: &Path) -> Result<()> {
const RETRIES: u32 = 20;
for attempt in 0..=RETRIES {
match temporary.persist(path) {
Ok(_) => return Ok(()),
Err(err) if should_retry_atomic_persist(&err.error) && attempt < RETRIES => {
temporary = err.file;
std::thread::sleep(Duration::from_millis(5 * u64::from(attempt + 1)));
}
Err(err) => return Err(err.error.into()),
}
}View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Walk the path from the message with `readlink` repeatedly to find where the loop closes
- Remove the offending symlink(s) (`rm <link>`, never `rm -r`) or repoint them at the real target directory
- Reinstall the affected tool version to rebuild clean state: `mise install -f <tool>@<version>`
- Prefer mise's own alias mechanism over manual install-dir symlinks, and keep MISE_DATA_PATH free of cyclic links
Example fix
# before: installs/node/22 -> installs/node/default -> installs/node/22 $ rm ~/.local/share/mise/installs/node/default $ mise alias set node 22 default # managed alias, no symlink loop
Defensive patterns
Strategy: try-catch
Validate before calling
# detect symlink loops in mise dirs before they break writes p="$HOME/.local/share/mise" for i in $(seq 1 41); do p=$(readlink "$p" 2>/dev/null) || break [ -n "$p" ] || break done
Try / catch
Catch write errors containing `too many symlinks while resolving atomic write target`; resolve and delete the cyclic link, then retry the mise command once — repeated failures mean the loop is elsewhere in the chain.
Prevention
- Use mise's alias mechanism instead of hand-rolling install-dir symlinks
- Keep MISE_DATA_PATH on plain directories, never inside symlink-cyclic layouts
- After restoring from backup, verify mise install dirs contain no unexpected links (`find ~/.local/share/mise -type l`)
When it happens
Trigger: Any mise atomic state/config write (settings, lockfile, cache) whose target path traverses a cyclic symlink chain — typically a self-referential or mutually-referential symlink inside the mise data/install directories, e.g. an install path alias pointing back into another alias of the same version.
Common situations: Hand-made symlinks sharing installs between version aliases that ended up cyclic; interrupted installs leaving half-replaced links; pointing MISE_DATA_PATH into a directory tree that symlinks back into itself; aggressive dotfile/sync setups linking cache dirs in a loop.
Related errors
- refusing to remove non-link: {}
- remote task path is not a regular file or directory: {}
- install from exe
- cannot write task stub because {} is a symbolic link
- cannot link {} to its own install path
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/e4ecbd2b4400f19c.
Report an issue: GitHub.