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

  1. Walk the path from the message with `readlink` repeatedly to find where the loop closes
  2. Remove the offending symlink(s) (`rm <link>`, never `rm -r`) or repoint them at the real target directory
  3. Reinstall the affected tool version to rebuild clean state: `mise install -f <tool>@<version>`
  4. 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

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


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