chenhg5/cc-connect · error

backup old binary: %w

Error message

backup old binary: %w

What it means

replaceBinary preserves the running binary by renaming it to execPath + ".old" before installing the new one; if os.Rename fails the temp file is removed and the error wrapped as "backup old binary: %w". Cross-device renames are the classic cause since rename cannot move across filesystems.

Source

Thrown at core/updater.go:279

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

	if err := os.Chmod(tmpPath, 0o755); err != nil {
		os.Remove(tmpPath)
		return fmt.Errorf("chmod: %w", err)
	}

	oldPath := execPath + ".old"
	os.Remove(oldPath)

	if err := os.Rename(execPath, oldPath); err != nil {
		os.Remove(tmpPath)
		return fmt.Errorf("backup old binary: %w", err)
	}

	if err := os.Rename(tmpPath, execPath); err != nil {
		// Try to restore
		if restoreErr := os.Rename(oldPath, execPath); restoreErr != nil {
			slog.Error("updater: failed to restore old binary after install failed", "error", restoreErr)
		}
		return fmt.Errorf("install new binary: %w", err)
	}

	// Don't remove .old file on Linux - the running process may still need it
	// for os.Executable() to work correctly after restart.
	// The .old file will be overwritten on next update.

	slog.Info("updater: binary replaced successfully", "path", execPath)
	return nil
}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check for a leftover <binary>.old that is a directory or owned oddly (ls -la next to the binary) and remove/fix it, then retry.
  2. Ensure the running user can write to the binary's directory (renaming requires directory write permission): sudo chown/chmod as needed.
  3. Remove the stale .old file first: sudo rm -f /path/to/cc-connect.old — the updater also tries os.Remove(oldPath) before renaming, but only as the same (possibly unprivileged) user.
  4. Check audit logs (SELinux/apparmor) if permission is denied unexpectedly and adjust policy.
  5. Restart from the current binary and retry; if deploys delete binaries concurrently, serialize deploys with the updater.

Example fix

# before
ls -la /opt/cc-connect
drwxr-xr-x cc-connect.old   # directory blocks rename
# after
sudo rm -rf /opt/cc-connect/cc-connect.old && sudo cc-connect update
Defensive patterns

Strategy: validation

Validate before calling

oldPath := execPath + ".old"
if info, err := os.Lstat(oldPath); err == nil && info.IsDir() {
    return fmt.Errorf("%s is a directory; remove it before updating", oldPath)
}
os.Remove(oldPath) // pre-clean like the updater does

Try / catch

if err := selfUpdate(); err != nil {
    if strings.Contains(err.Error(), "backup old binary") {
        // remove stale cc-connect.old, ensure dir write permission, retry
    }
}

Prevention

When it happens

Trigger: SelfUpdate -> replaceBinary: os.Rename(execPath, oldPath) failed — the .old target is a directory, the binary's directory is on a filesystem where rename of the open executable is restricted (e.g. some Windows lock semantics in emulated environments), permission denied on the directory (write permission needed to rename within it), or the path disappeared after EvalSymlinks.

Common situations: A stale cc-connect.old directory (not file) left by a previous manual intervention; directory made read-only after the temp file was created via different privileges; SELinux policy blocking rename of a running executable; execPath deleted concurrently by a deploy script.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/b8cd73817753f56f. Report an issue: GitHub.