Cask upgrade: failed cleanup of the old version's backup reverts a completed install #7047
Replies: 1 comment
|
I can't speak for maintainer intent, but the failure mode you described is still present in current In install_artifacts(predecessor:)
# write Tab / analytics
purge_backed_up_versioned_files
puts summary
rescue => e
begin
restore_backup
ensure
purge_staged_download
end
raise e
endSo an exception from And your second point also matches current source: def restore_backup
# restores staged + metadata directories
end
def revert_upgrade(predecessor:)
restore_backup
install_artifacts(predecessor:)
endThe generic Current source: https://github.com/Homebrew/brew/blob/main/Library/Homebrew/cask/installer.rb I'd separate two issues:
Your root-owned subtree/no-TTY sudo case is one trigger, but the control-flow problem does not depend on that specific trigger; any exception from the purge reaches the same rescue. A Tier-1 reproduction would be worth filing as an issue. For a fix, either of these is defensible:
I'd lean toward the first because failure to remove an old backup should not invalidate an already successful new install, but that policy choice is for Homebrew maintainers. The important part for a bug report is that current code can leave the cask in a state that neither represents the new install nor a fully restored predecessor. |
Uh oh!
There was an error while loading. Please reload this page.
Output of
brew configOutput of
brew doctorDescription of issue
Cleanup of the previous version's backup runs inside the install's success path, so any
failure to delete that backup discards a cask install that has already completed — and the
rescue that handles it is less complete than
revert_upgrade, leaving the cask with noartifacts installed at all.
Posting as a discussion rather than a bug report because the incident is repaired and I can
no longer reproduce it on demand. Disclosure: the analysis and this write-up were
drafted with Claude Code (Claude Opus 5); I will answer any follow-up questions myself.
What happens
In
Cask::Installer#install,purge_backed_up_versioned_filesruns after the new versionis fully installed — after
install_artifacts, afterTab.create/tab.write, afteranalytics — but still inside the same
begin/rescue:If the previous version's
<staged_path>.upgradingbackup cannot be deleted, that raises,and the rescue calls
restore_backup, whichrm_rs the newly installed staging directoryand renames the old backup over it. A completed install is discarded because cleanup of the
previous version's backup failed.
Two compounding problems:
restore_backup, unlikerevert_upgrade, which isrestore_backup+install_artifacts(predecessor:). So the restored old version nevergets its artifacts reinstalled. The cask ends with no
.desktop, no linked binaries, andits
Movedartifacts sitting as real files in the staged directory.That last state is unrecoverable by normal means: every subsequent
brew upgradefails inMoved#move_backatUtils.path_occupied?(source)withbecause
move_backexpects that path to be absent or a symlink to the target. Onlybrew uninstall --cask --forceclears it.How the delete failed here
Any
EPERM/EACCESfromrmtreereaches this path — a root-owned or immutable file, aread-only mount, a full disk during the rename, a stale handle on a network filesystem.
Mine was ownership: a third-party cask's
postflighthad chowned a subtree of its Caskroomstaging directory to
root:root. On upgrade,rmtreeof the backup failed on ownership, soCask::Utils.gain_permissions_remove(cask/utils.rb:123) fell back towhich ran with no controlling terminal (
terminal=?in the audit record) and could notprompt for a password. I am reporting the root-owning separately to that tap; the question
here is only about the revert behaviour.
Suggested reproduction
brew install --cask <any cask with a Moved artifact>sudo chown -R root:root "$(brew --prefix)/Caskroom/<token>/<version>/<subdir>"SUDO_ASKPASSunsetbrew upgrade --cask <token>Expected: the new version installs, the purge fails, the install is reverted, and the cask is
left with nothing installed. Re-running
brew upgradethen fails permanently inmove_back.I have not run this on a Tier 1 prefix. Nothing in the path appears to depend on the prefix
layout — the raise comes from a failed delete and the revert is unconditional — but that is
an inference, not a tested claim.
Suggested fix
purge_backed_up_versioned_filesin its ownrescue that
opoos, or move it afterputs summary, outside the guarded region.revert_upgradesemantics, so a reverted cask is left with itsartifacts installed rather than half-uninstalled.
Moved#move_back's error mention--force, since the wedged state has noother exit.
Question
Is the revert-on-cleanup-failure behaviour intended? If not, I am happy to build a
reproduction on a Tier 1 prefix and open a proper issue.
All reactions