multica-ai/multica · error

move running binary aside: %w

Error message

move running binary aside: %w

What it means

Windows-only replaceBinary failure: os.Rename(exePath, exePath+".old") could not move the currently running executable aside. Windows locks a running .exe against delete/rename-in-place, so this fires when something holds an unexpected handle on the binary, the directory denies write access, or the previously removed .old path is still locked by another process.

Source

Thrown at server/internal/cli/update_windows.go:36

// replaceBinary swaps the running executable for the freshly-downloaded one.
// Windows holds an exclusive handle on a running .exe, so the rename-over
// pattern used on Unix fails with "Access is denied". Instead:
//  1. Clear any stale leftover from a previous update.
//  2. Move the running executable aside to exePath+".old".
//  3. Rename the new binary into place.
//  4. If step 3 fails, restore the original so the user isn't stranded.
//
// The leftover .old file is cleaned up on next startup via
// CleanupStaleUpdateArtifacts.
func replaceBinary(tmpPath, exePath string) error {
	oldPath := exePath + oldBinarySuffix

	// Best-effort cleanup; if this fails (file still locked) the next Rename
	// will surface a useful error.
	_ = os.Remove(oldPath)

	if err := os.Rename(exePath, oldPath); err != nil {
		return fmt.Errorf("move running binary aside: %w", err)
	}

	if err := os.Rename(tmpPath, exePath); err != nil {
		// Restore so the user isn't left without a multica.exe.
		if rerr := os.Rename(oldPath, exePath); rerr != nil {
			return fmt.Errorf("install new binary: %w (and failed to restore: %v)", err, rerr)
		}
		return fmt.Errorf("install new binary: %w", err)
	}

	return nil
}

// CleanupStaleUpdateArtifacts removes leftover `.old` binaries from previous
// updates. Windows can't delete a running .exe, so a prior update may have
// left one behind; once the user restarts, this call reclaims the space.
func CleanupStaleUpdateArtifacts() {
	exePath, err := selfexec.Resolve()

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Stop all multica processes (taskmgr / Get-Process multica) and retry the update
  2. Add an AV exclusion for the install directory, or retry after a few seconds so the AV scan handle is released
  3. Move the install to a user-writable directory (or run the update elevated if under Program Files)
  4. If a stale .old file is the problem, reboot (releases the lock) so CleanupStaleUpdateArtifacts can remove it, then update
Defensive patterns

Strategy: retry

Validate before calling

// Before replacing, check for other running instances of this exe.
func otherInstancesRunning() bool {
    self, _ := os.Executable()
    procs, _ := gops.Processes() // or enumerate via tasklist on Windows
    for _, p := range procs {
        if exe, err := p.Exe(); err == nil && strings.EqualFold(exe, self) && p.Pid() != os.Getpid() {
            return true
        }
    }
    return false
}

Try / catch

Catch sharing-violation errors (syscall.ERROR_SHARING_VIOLATION via errors.As on *os.LinkError/*os.PathError) and retry the rename 2-3 times with a short backoff before giving up.

Prevention

When it happens

Trigger: Running the self-update while a second multica instance (or a crashed child process) still holds the exe open, antivirus scanning the binary at the moment of rename, insufficient NTFS permissions on the install directory, or the exe living on a read-only/network mount.

Common situations: Two daemons started from the same install path, AV/EDR agents briefly locking freshly downloaded binaries, running from a mounted share, or install dirs under Program Files without elevation.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/55ab966b39367b15. Report an issue: GitHub.