multica-ai/multica · error

stat original binary: %w

Error message

stat original binary: %w

What it means

To preserve the original file mode, the updater stats the current binary (os.Stat(exePath)) before chmod-ing the temp file; failure is wrapped as 'stat original binary: %w' and the temp file is removed. It means the binary path resolved at the start of the update no longer exists at commit time.

Source

Thrown at server/internal/cli/update.go:464

	dir := filepath.Dir(exePath)
	tmpFile, err := os.CreateTemp(dir, "multica-update-*")
	if err != nil {
		return "", fmt.Errorf("create temp file: %w", err)
	}
	tmpPath := tmpFile.Name()

	if _, err := tmpFile.Write(binaryData); err != nil {
		tmpFile.Close()
		os.Remove(tmpPath)
		return "", fmt.Errorf("write temp file: %w", err)
	}
	tmpFile.Close()

	// Preserve original file permissions.
	info, err := os.Stat(exePath)
	if err != nil {
		os.Remove(tmpPath)
		return "", fmt.Errorf("stat original binary: %w", err)
	}
	if err := os.Chmod(tmpPath, info.Mode()); err != nil {
		os.Remove(tmpPath)
		return "", fmt.Errorf("chmod temp file: %w", err)
	}

	// Replace the original binary. On Windows this moves the running executable
	// aside first; on Unix a plain rename over the running inode is fine.
	if err := replaceBinary(tmpPath, exePath); err != nil {
		os.Remove(tmpPath)
		return "", fmt.Errorf("replace binary: %w", err)
	}

	return fmt.Sprintf("Downloaded %s and replaced %s", assetName, exePath), nil
}

// extractBinaryFromTarGz reads a .tar.gz stream and returns the contents of the
// named file entry.

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Ensure only one updater runs at a time (lock file / single instance of the daemon).
  2. If the binary was uninstalled or moved, reinstall it at the expected path and retry the update.
  3. Check the wrapped fs.PathError for ENOENT vs EACCES to distinguish deleted vs unreadable.
  4. Restart the daemon after manual binary changes so it re-resolves its path.

Example fix

null
Defensive patterns

Strategy: validation

Validate before calling

exe, _ := selfexec.Resolve()
exe, _ = filepath.EvalSymlinks(exe)
if _, err := os.Stat(exe); err != nil {
    // binary moved/deleted since start: re-resolve before updating
}

Try / catch

out, err := cli.UpdateViaDownload(ver)
if err != nil {
    var pe *fs.PathError
    if errors.As(err, &pe) && errors.Is(pe.Err, fs.ErrNotExist) && strings.HasPrefix(err.Error(), "stat original binary") {
        // concurrent replace/uninstall raced us: restart the daemon and retry
    }
}

Prevention

When it happens

Trigger: Another updater/process replaced or deleted the binary between Resolve and this point; the user uninstalled while the download was in flight; the symlink target vanished mid-update; the directory permissions changed.

Common situations: Two update pollers racing (systemd service plus a CLI run); a package-manager uninstall racing the daemon's self-update; dev environments rebuilding/removing binaries during a long download.

Related errors


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