Skip to content

fix(update): stop crashing on Windows after a successful update - #1924

Merged
Aaronontheweb merged 4 commits into
devfrom
fix/windows-update-backup-cleanup
Aug 14, 2026
Merged

fix(update): stop crashing on Windows after a successful update#1924
Aaronontheweb merged 4 commits into
devfrom
fix/windows-update-backup-cleanup

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Collaborator

Fixes #1923

Problem

netclaw update on Windows swaps the running CLI binary, restarts the daemon, then crashes with an unhandled UnauthorizedAccessException while deleting netclaw.exe.backup in the post-update cleanup loop.

Root cause: the install step renames the currently running netclaw.exe to netclaw.exe.backup (rename of a running image is allowed on Windows), then cleanup calls File.Delete on it. Windows refuses to delete the image a process is still executing from. The update had already succeeded — the crash was cosmetic cleanup turning success into a fatal error.

Fix

src/Netclaw.Cli/Update/UpdateCommand.cs

  1. Skip deleting the running process's own backup on Windows. The skip keys on the actual backup path (Environment.ProcessPath + ".backup"), not component-name matching, so it also covers daemon self-update. The leftover self-heals: the install step deletes stale backups before renaming on the next update.
  2. Any other backup-delete failure only warns (extracted into CleanupBackupFile) — a leftover backup must never turn a successful update into a fatal error.
  3. Install-step backup swap is hardened against transient lock failures (e.g., AV scan holding the stale .backup): fail loudly with a clear message and return 1 instead of crashing mid-swap.

On Linux/macOS the rename/delete of a running image is safe (POSIX unlink semantics), so behavior there is unchanged — backups are still created and cleaned up.

Tests

Added 4 unit tests in UpdateCommandTests.cs covering:

  • running-image backup is left in place on Windows (the regression)
  • other-component backups are still deleted on Windows
  • backups are deleted on non-Windows even for the running image
  • delete failure warns instead of throwing

All 42 UpdateCommandTests pass; build clean; slopwatch no new violations; file headers verified.

Notes

  • No OpenSpec change: targeted bug fix.
  • No system-skill change: updater flow is not mapped to a skill in the constitution table.

The post-update cleanup tried to delete the backup of the currently
running CLI binary. On Windows, DeleteFile on a running image fails
with UnauthorizedAccessException, turning a successful update into a
fatal crash.

Skip deleting the running process's own backup on Windows; the install
step removes stale backups before renaming on the next update. Treat
any other backup-delete failure as a warning, and harden the install
step's backup swap against transient lock failures with a clear error
instead of an unhandled exception.

Closes #1923
@Aaronontheweb Aaronontheweb added the platform:windows Windows-specific issues and support label Aug 13, 2026
Comment thread src/Netclaw.Cli/Update/UpdateCommand.cs Fixed
Comment on lines +545 to +548
catch (Exception ex)
{
Console.Error.WriteLine($"warn: could not remove backup {backupPath}: {ex.Message}");
}
[Fact]
public void CleanupBackupFile_DoesNotDelete_RunningImageBackup_OnWindows()
{
var backupPath = Path.Combine(_dir.Path, "netclaw.exe.backup");
Comment thread src/Netclaw.Cli.Tests/Cli/UpdateCommandTests.cs Fixed
Comment thread src/Netclaw.Cli.Tests/Cli/UpdateCommandTests.cs Fixed
Comment thread src/Netclaw.Cli.Tests/Cli/UpdateCommandTests.cs Fixed
Comment thread src/Netclaw.Cli.Tests/Cli/UpdateCommandTests.cs Fixed
A failed swap could leave the install directory without a binary:
the old executable was renamed to .backup, then the new one failed
to move into place. On Windows that bricks the CLI until the user
manually restores the .backup.

SwapBinaryIntoPlace now restores the previous binary to the target
path when the new binary fails to move, and reports the rollback
outcome in the error message. The install step also tells the user
the daemon is stopped and how to recover.

Adds tests for the swap success path, rollback on move failure, and
stale-backup delete failure leaving the target intact.
Comment on lines +548 to +553
catch (Exception rollbackEx)
{
// Best-effort rollback; the original swap failure is
// rethrown below and reported to the user.
Console.Error.WriteLine($"warn: failed to restore {targetPath} from {backupPath}: {rollbackEx.Message}");
}
// from; on Windows DeleteFile fails with UnauthorizedAccessException.
// NTFS path comparison is case-insensitive, so pin that here — a
// regression to Ordinal would leave the backup deleted.
var runningBackupPath = Path.Combine(_dir.Path, "NETCLAW.EXE.BACKUP");
[Fact]
public void SwapBinaryIntoPlace_ReplacesTarget_AndBacksUpOldBinary()
{
var sourcePath = Path.Combine(_dir.Path, "new.exe");
public void SwapBinaryIntoPlace_ReplacesTarget_AndBacksUpOldBinary()
{
var sourcePath = Path.Combine(_dir.Path, "new.exe");
var targetPath = Path.Combine(_dir.Path, "netclaw.exe");
[Fact]
public void SwapBinaryIntoPlace_RestoresOldBinary_WhenNewBinaryMoveFails()
{
var sourcePath = Path.Combine(_dir.Path, "new.exe");
public void SwapBinaryIntoPlace_RestoresOldBinary_WhenNewBinaryMoveFails()
{
var sourcePath = Path.Combine(_dir.Path, "new.exe");
var targetPath = Path.Combine(_dir.Path, "netclaw.exe");
if (OperatingSystem.IsWindows() || Environment.UserName == "root")
return; // permission simulation below is Unix-only and ineffective for root

var sourcePath = Path.Combine(_dir.Path, "new.exe");
@Aaronontheweb Aaronontheweb added the bug Something isn't working label Aug 13, 2026
The chmod-based file-lock simulation tests used an inline
`if (OperatingSystem.IsWindows() || root) return;` early-exit, which
reports as Passed on unsupported platforms. Switch to the repo
standard used by the shell-approval suite: a static SkipUnless hook
(`CanSimulateFileLock`) plus a local SlopwatchSuppressAttribute.

CA1416 still requires a recognized platform guard for the
File.GetUnixFileMode/SetUnixFileMode calls (xUnit attributes are
invisible to the analyzer), so the Windows early-return stays as the
analyzer guard — matching SecretsFileWriterTests, the repo's other
UnixFileMode test precedent.
@Aaronontheweb
Aaronontheweb merged commit 654d5b5 into dev Aug 14, 2026
23 checks passed
@Aaronontheweb
Aaronontheweb deleted the fix/windows-update-backup-cleanup branch August 14, 2026 00:34
@Aaronontheweb Aaronontheweb mentioned this pull request Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working platform:windows Windows-specific issues and support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

netclaw update crashes on Windows after successful install (UnauthorizedAccessException deleting netclaw.exe.backup)

1 participant