Repository navigation
fix(cli): keep Windows upgrades and uninstalls from fighting the running binary - #50819
Merged
Merged
Conversation
Hona
force-pushed
the
windows-upgrade-lock
branch
from
September 23, 2026 04:09
6be78c5 to
866a43d
Compare
Hona
marked this pull request as ready for review
September 23, 2026 04:18
Hona
force-pushed
the
windows-upgrade-lock
branch
2 times, most recently
from
September 23, 2026 06:07
6f14928 to
9374fb9
Compare
…ing binary Windows refuses to delete the last hard link of a running executable, and bun 1.4 spins forever when its package replacement meets one (src/sys/dir.rs delete_tree treats the resulting EPERM as a directory). opencode upgrade installs through a throwaway bun cache, so node_modules holds the only links to the running binary and the next upgrade turns it into the last link. Any other link to the image can be deleted, so: - RetainedImage keeps a second hard link to the running image for the lifetime of a scope and sweeps links of exited processes. - updater: hold that link around every installer run (cache for upgrades, the OpenCode temporary directory for uninstall, which has already removed the cache); only for the installed binary. - service: hold it while serve --service runs, so a package manager invoked by hand can still replace the installed copy. - uninstall: retry directory removal while Windows still reports the terminated service's database handles as busy. Unix and macOS paths are unchanged.
Hona
force-pushed
the
windows-upgrade-lock
branch
from
September 23, 2026 06:27
9374fb9 to
29b9f97
Compare
Hona
enabled auto-merge (squash)
September 23, 2026 06:29
3 of 6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
opencode upgradeon Windows hangs for five minutes and then printsFailed to update with bun. This PR makes upgrade, uninstall and manual package-manager upgrades work while OpenCode is running on Windows. Unix and macOS paths are untouched.Root cause
deleteex: "so long as it's not the last link to the file";STATUS_CANNOT_DELETE= "existing mapped view", MS-FSCC 2.4.11.1; libuvfs.csays the same in itsfs__unlinkcomment). Measured here: non-last link deletes from Win32, node and bun; the last link fails withEPERM.updater.tsinstalls through a throwaway--cache-dirand deletes it, sonode_modulesholds the only links to the binary. The next upgrade removes the platform package's link first and the running@opencode/cli/bin/opencode.exebecomes the last link.src/sys/dir.rsdelete_treeloops forever on thatEPERM— a regression from 1.3.13, which left an orphan and finished (bun install hangs forever on Windows when the replaced package holds the last hard link of a running exe (regression from 1.3.13) oven-sh/bun#43831). Every.old-*orphan in~/.bun/install/global/node_moduleshaslinks=1— same mechanism..cli-*orphan withnpm warn cleanup … EPERM; Git Bashmv(curl installer) hitsSTATUS_ACCESS_DENIEDon the in-use target and falls back to moving the 200 MB binary into the Recycle Bin (Cygwinsyscalls.ccrename2→unlink_nt→try_to_bin);uninstallreportsBusybecause libuv'skill(pid, 0)isGetExitCodeProcess != STILL_ACTIVEwhileTerminateProcess"is asynchronous" and the service's SQLite handles close a moment later.Change (win32 only)
Keep a second hard link to the running image for as long as it must survive an installer. The installed path never disappears; installers delete a non-last link and succeed.
Only binaries installed by a package manager or the curl installer are linked: never bun/node in a source checkout, never the Desktop app's bundled CLI (Desktop updates it itself). The link is skipped with a warning when the cache is on another volume, in which case behaviour is exactly as before this PR.
Results
Each cell is a real run in an isolated install root with its own managed-service port. Baseline = published
@opencode/[email protected](itsupdater.tsis identical tov2). Candidate = this branch compiled as 2.0.13. Every run upgrades 2.0.13 → 2.0.14 and checks the binary on disk, the manifest/lockfile, orphaned directories and the Recycle Bin.✅ completed, consistent, nothing left behind ·⚠️ completed but left a 200 MB orphan dir or Recycle Bin deposit · ❌ hung or failed
opencode upgrade— service stopped (only the upgrader runs the binary)opencode upgrade— service running (same for TUI/upgrade:update-notification.tsx→updater.apply→Updater.upgrade)Manual package-manager upgrade in a shell — nothing running (control)
bun install -g --trust @opencode/[email protected])npm install -g --force @opencode/[email protected])pnpm add -g --allow-build=@opencode/cli @opencode/[email protected])bash install --version 2.0.14)Manual package-manager upgrade in a shell — service running
bun install -g --trust @opencode/[email protected])npm install -g --force @opencode/[email protected])pnpm add -g --allow-build=@opencode/cli @opencode/[email protected])bash install --version 2.0.14)opencode uninstall --force— service runningNotes on the grid:
--methodto pin the installer; the same fourservice runningupgrades were also run without it, and auto-detection picked the right installer every time./upgradeshares theopencode upgrade — service runningrow:update-notification.tsx→updater.apply→Updater.install→Updater.upgrade..pnpm/@opencode+cli@xentries untilpnpm store prune; that is normal and not counted.uninstallfailures are theBusyrace (Failed to remove Data); it is timing-dependent — pnpm baseline passed in two runs and failed in a third.opencode uninstallwhile an older OpenCode still runs from a cache link (e.g. a TUI that ran/upgrade). Candidate: all directories removed, the old process keeps running, its link moves to%TEMP%\opencode. Same setup with the published 2.0.13:Failed to remove Cache.bashonPATH; with WSLbashfirst, the installer runs under Linux, which is a pre-existing Windows issue not touched here.Compared with macOS / Linux
Same user-visible semantics:
opencode upgradeand/upgradecomplete while OpenCode runs, already-running processes keep the old version until restarted, new launches get the new one, and a hand-typed package-manager upgrade works while the service runs. Windows-only differences:~\.cache\opencode(0 bytes)%TEMP%\opencodeuntil Storage Sense (if enabled) or the user removes it; nothing in OpenCode runs after an uninstall%TEMP%on another volume than the cacheNot run
/upgradewas not driven interactively; it calls the sameUpdater.upgrade.~\.cacheon another volume, ARM64/baseline binaries.Not in this PR
Uninstall stops managed services only; any other OpenCode still running (TUI,
serve) keeps the database open and Windows refuses to delete Data. Unchanged by this PR.Deleting the uninstaller's own binary after exit needs a self-deleting helper (as
self-replace/rustup do); a bun-compiled binary has no hook for it. Possible follow-up.An older client still replaces a newer running service (
default.tsmismatch: "replace"), which is how a 2.0.8 shell client "downgraded" a Desktop-managed 2.0.14 service. Separate change.bun's
delete_treeEPERM loop is bun's bug, reported with a 10 MB self-contained repro and a 1.3.13-vs-1.4.2 measurement in bun install hangs forever on Windows when the replaced package holds the last hard link of a running exe (regression from 1.3.13) oven-sh/bun#43831; this PR only stops OpenCode from triggering it.