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.
Summary
scripts/install.shreplaces the installed binary withmv -f, which cannot succeed on Windows when the target executable is running.scripts/install.ps1already solves this and carries the rationale in a comment; the shell installer never got the same treatment.Detail
scripts/install.sh:664:scripts/install.ps1:667-675handles the identical step, with the reason stated inline:Note the PowerShell version also restores the moved-aside binary from a
finallyblock when placement fails — the shell script has no equivalent recovery either.Impact
uloop updateon Windows routes throughinstall.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 auloop.exeis 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:
mvreports 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.