Skip to content

fix: Git Bash installer cannot replace a running uloop.exe on Windows #2367

Description

@hatayama

Summary

scripts/install.sh replaces the installed binary with mv -f, which cannot succeed on Windows when the target executable is running. scripts/install.ps1 already solves this and carries the rationale in a comment; the shell installer never got the same treatment.

Detail

scripts/install.sh:664:

mv -f "$staged_uloop_path" "$final_uloop_path"

scripts/install.ps1:667-675 handles the identical step, with the reason stated inline:

# Why: Windows locks the image file of a running executable against
# overwrite and delete but still allows rename. `uloop update` runs this
# script as a child of the running uloop.exe, so overwriting the target in
# place can never succeed there. Move the existing binary aside first; the
# finally block restores it if the new binary was not placed.
if (Test-Path -LiteralPath $FinalUloopPath) {
    $ReplacedUloopBackupPath = $FinalUloopPath + ".old-" + [System.Guid]::NewGuid().ToString("N")
    Move-Item -LiteralPath $FinalUloopPath -Destination $ReplacedUloopBackupPath -Force
}
Copy-Item -Path $StagedUloopPath -Destination $FinalUloopPath -Force

Note the PowerShell version also restores the moved-aside binary from a finally block when placement fails — the shell script has no equivalent recovery either.

Impact

uloop update on Windows routes through install.ps1, so the self-update path is safe. The exposed case is a user re-running the documented Git Bash install line to upgrade while a uloop.exe is live — for example an editor, an agent session, or a background command still holding the image.

Scope is therefore narrower than the PowerShell path, but the failure is confusing: mv reports a permission error on a file the user owns.

This shares a root cause with the Git Bash route already tracked in #2365 — both are places where the shell installer's Windows path lacks a guard the PowerShell installer has.

Suggested fix

Mirror the PowerShell approach: if the destination exists, rename it aside to a unique name first, place the new binary, and remove the backup on success. On failure, restore the backup. Renaming aside is harmless on macOS and Linux, so the code path does not need to be platform-conditional.

Provenance

Found by a read-only Windows compatibility audit (finding F7). Line numbers verified against the current tree. No files were modified.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions