jdx/mise · critical

{target} on '{}' changed after it was installed; another wri

Error message

{target} on '{}' changed after it was installed; another writer owns that path

What it means

After installing mise on the remote host, mise computes the SHA-256 of the installed executable and compares it with the digest of what it just wrote. A mismatch means something else rewrote the install path between write and verification, so mise refuses to run an executable it cannot trust.

Source

Thrown at src/system/remote.rs:1080

    if remote_executable_sha256(session, target)
        .await?
        .is_some_and(|installed| installed == expected)
    {
        info!(
            "mise is already installed at {target} on {}",
            session.host.name
        );
        return Ok(());
    }
    let file = File::open(binary)
        .wrap_err_with(|| format!("failed to open mise binary {}", binary.display()))?;
    session
        .status_with_stdin_async(&["sh", "-c", &install_mise_script(target)], file)
        .await?;
    // An install path can be writable by more than the SSH account, so confirm
    // the executable about to run is the one that was just written.
    match remote_executable_sha256(session, target).await? {
        Some(installed) if installed != expected => bail!(
            "{target} on '{}' changed after it was installed; another writer owns that path",
            session.host.name
        ),
        Some(_) => {}
        None => warn!(
            "cannot verify the mise installed at {target} on {}; the host returned no SHA-256 digest",
            session.host.name
        ),
    }
    info!("installed mise to {target} on {}", session.host.name);
    Ok(())
}

/// Writes beside the target and renames, so a replaced executable is never
/// truncated in place and a busy binary cannot fail with ETXTBSY. The write
/// goes into a `mktemp -d` directory rather than a named sibling file: that
/// directory is private to the SSH account, so nobody else can swap the path
/// out from under `cat` in a shared writable install directory. A directory at

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Install to a user-owned path (e.g. ~/.local/bin/mise) only writable by the SSH account
  2. Stop concurrent processes/jobs that also write to that install path
  3. Re-run the install; if it keeps failing, investigate who else owns the path (ls -la, chattr)
  4. Tighten permissions on the target directory so only the install account can write

Example fix

// before: shared writable path
MISE_INSTALL_PATH=/usr/local/bin/mise
// after: account-private path
MISE_INSTALL_PATH=$HOME/.local/bin/mise
Defensive patterns

Strategy: try-catch

Validate before calling

const expectedSha = crypto.createHash('sha256').update(fs.readFileSync(localMiseBinary)).digest('hex');
// ensure remote install dir is owned and writable only by the SSH account before install

Try / catch

try { await installRemoteMise(); } catch (e) { if (String(e).includes('changed after it was installed')) { quarantinePath(target); await installRemoteMise(); } throw e; }

Prevention

When it happens

Trigger: remote_executable_sha256 returns a digest different from the expected one right after status_with_stdin_async ran install_mise_script(target) — i.e. the file at target was replaced or modified by another writer on the host.

Common situations: Shared multi-user hosts where MISE_INSTALL_PATH (e.g. /usr/local/bin/mise) is writable by other accounts or by another concurrent mise bootstrap; CI runners with a pre-seeded mise at the same path.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/2d7924009c9fb9ee. Report an issue: GitHub.