Kuberwastaken/claurst · error

failed to install new binary

Error message

failed to install new binary: {}

What it means

The in-place binary replacement failed: renaming/moving the new binary into the current executable's path errored (the formatted value is the underlying io error). On Windows the running exe must be sidelined first; failure typically means the file is locked by a running instance or the directory denies writes.

Solutions

  1. Close all running claurst instances and retry the upgrade
  2. Check write permissions on the install directory (may need elevated privileges)
  3. Delete leftover .exe.old / staging files that may block the rename, then re-run
Defensive patterns

Strategy: fallback

When it happens

Trigger: Thrown at src-rust/crates/cli/src/upgrade.rs:371 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/7a770bb50769b3b2. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/cli/src/upgrade.rs:371

    fn drop(&mut self) {
        let _ = std::fs::remove_file(&self.path);
    }
}

fn swap_binary(current: &Path, new: &Path) -> Result<()> {
    #[cfg(target_os = "windows")]
    {
        // Windows holds an exclusive lock on the running .exe — we can rename
        // it but not unlink it, so move-aside, then move-in-place.
        let mut sidelined = current.to_path_buf();
        sidelined.set_extension("exe.old");
        let _ = std::fs::remove_file(&sidelined);
        std::fs::rename(current, &sidelined)
            .with_context(|| format!("failed to sideline current exe to {}", sidelined.display()))?;
        if let Err(e) = std::fs::copy(new, current) {
            // Try to roll back the rename so the user isn't left without claurst.
            let _ = std::fs::rename(&sidelined, current);
            bail!("failed to install new binary: {}", e);
        }
        // Best-effort cleanup of the old exe (Windows may keep it locked
        // until the running process exits — that's fine).
        let _ = std::fs::remove_file(&sidelined);
        Ok(())
    }

    #[cfg(unix)]
    {
        use std::io::Write;
        use std::os::unix::fs::PermissionsExt;

        // std::fs::copy() writes into the existing inode (open + O_TRUNC), and
        // that inode is the running process's own text segment — the kernel
        // rejects the write with ETXTBSY ("Text file busy"). rename() instead
        // swaps the directory entry to point at a new inode, which the kernel
        // allows even while the old inode is still mapped and executing.
        // rename() requires src/dest on the same filesystem, so stage the

View on GitHub (pinned to b0637c97ec)