Skip to content

fix: repair rb update on Windows and wire the background update check#37

Merged
a-essawy merged 3 commits into
mainfrom
fix/windows-update-self-replace
Jun 6, 2026
Merged

fix: repair rb update on Windows and wire the background update check#37
a-essawy merged 3 commits into
mainfrom
fix/windows-update-self-replace

Conversation

@a-essawy

@a-essawy a-essawy commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

What

rb update extracted the release archive straight into the running binary's own directory. The archive payload (rb / rb.exe) shares the live executable's name, so:

  1. Expand-Archive -Force (Windows) / tar -xzf tried to overwrite the running, locked exe. On Windows the archiver's internal Remove-Item fails with Access to the path '...\rb.exe' is denied.
  2. The extractedPath !== binPath guard was then false (both resolved to <binDir>/rb.exe), so the .new staging rename was skipped — leaving the run to die with extracted binary not found at expected location.

Net effect: rb update was completely broken on Windows.

Fix

Extract into an isolated .rb-update-<ts> temp dir, then stage the extracted binary as .new and rename-swap into place. Renaming a running binary is permitted on Windows; deleting it is not — which is exactly what the existing .bak/.new swap relies on. The temp dir is always cleaned via finally.

The existing .bak one-version rollback and the post-swap --version verification are unchanged.

Test plan

Verified end-to-end on Windows 11 (the platform that was failing):

  • Compiled a standalone rb.exe pinned to 1.2.0 (same --define IS_STANDALONE / PLATFORM flags as cli-binaries.yml).
  • Ran rb update against the live 1.3.0 release:
    • checksum verified → Update complete → exit 0
    • rb.exe replaced in place (114154496 → 98544640 bytes), rb.exe --version1.3.0
    • rb.exe.bak preserved for rollback
    • no leftover .rb-update-* temp dir, no stray .new
  • Re-ran rb updateAlready on the latest version (1.3.0), exit 0 (clean idempotent no-op).

pnpm typecheck green. The 8 failing tests in this environment are pre-existing spawn 'bun' ENOENT integration tests (local bun is a .ps1 shim node can't exec) — unrelated to this change.

a-essawy added 2 commits June 7, 2026 00:58
rb update extracted the release archive straight into the binary's own
directory. The payload (rb / rb.exe) shares the running executable's name,
so Expand-Archive -Force tried to overwrite the live, locked exe. On Windows
its internal Remove-Item fails with "Access to the path is denied", aborting
the update. The extractedPath !== binPath guard was then false (both resolved
to the same rb.exe), so the .new staging rename was skipped and the run died
with "extracted binary not found at expected location".

Extract into an isolated .rb-update-<ts> temp dir instead, then stage the
result as .new and rename-swap into place. Renaming a running binary is
permitted on Windows; deleting it is not. The existing .bak rollback and
post-swap --version verification are unchanged. The temp dir is always
removed via finally.

Verified end-to-end on Windows: a pinned 1.2.0 standalone self-replaced to
the live 1.3.0 release, kept rb.exe.bak for rollback, left no temp dirs, and
re-ran as a clean no-op.
The "Update available" notice on the welcome screen read a cache that nothing
ever populated: checkForUpdate() was only ever called from tests, so the cache
stayed empty and getPendingNotification() always returned null. The notice
could never fire on its own.

A naive inline call cannot fix this — short-lived commands (the welcome screen
exits immediately) terminate long before the GitHub round-trip completes, so
the fetch would be killed mid-flight. Instead spawn a detached, unref'd
`rb __update-check` process on launch that outlives the command and warms the
cache for the next run (the same approach gh and update-notifier use).

- add isRefreshDue(): cheap sync gate (honors skip rules + 24h TTL) so a fresh
  cache costs zero subprocesses
- add the hidden __update-check entrypoint in main.ts; gate it before routing
- spawn the detached refresh only in standalone builds (IS_BIN); dev mode's
  execPath is bun, not rb
- skip entirely in CI and under --quiet/--json/--url-only (existing shouldSkip)

Verified end-to-end on Windows: a pinned 1.2.0 standalone spawned the detached
check, the cache filled with 1.3.0, and the next run rendered
"Update available: 1.2.0 -> 1.3.0". --quiet produced no output and no spawn.
@a-essawy a-essawy changed the title fix: extract rb update archive to temp dir, not over the running binary fix: repair rb update on Windows and wire the background update check Jun 6, 2026
@a-essawy a-essawy merged commit 69f2a73 into main Jun 6, 2026
2 checks passed
@a-essawy a-essawy deleted the fix/windows-update-self-replace branch June 6, 2026 22:27
This was referenced Jun 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant