Skip to content

cask: fix migration when the new cask is already installed#23076

Merged
MikeMcQuaid merged 2 commits into
mainfrom
cask-migrator-both-installed
Jul 13, 2026
Merged

cask: fix migration when the new cask is already installed#23076
MikeMcQuaid merged 2 commits into
mainfrom
cask-migrator-both-installed

Conversation

@bevanjkay

@bevanjkay bevanjkay commented Jul 13, 2026

Copy link
Copy Markdown
Member

When a cask is renamed to a cask that is already installed, the migration silently does nothing and the old cask is left behind in the Caskroom.

Cask::Migrator.migrate_if_needed worked out which token to migrate from new_cask.installed_caskfile. That lookup returns the first match, so with both casks installed it always found the new cask's own caskfile, saw that the installed token already matched, and returned early.

The leftover directory then loads as the cask it was renamed to, because the loader resolves the rename before it falls back to the installed caskfile. Cask::Caskroom.casks returns the same cask twice, so it gets listed, reported as outdated and upgraded twice:

==> Outdated Casks
chatgpt
chatgpt

==> Upgrading 2 outdated packages:
chatgpt 1.2026.183,1783607847 -> 26.707.51957
chatgpt 1.2026.183,1783607847 -> 26.707.51957

There's also no way out of that state, since brew uninstall --cask codex-app resolves the rename and uninstalls chatgpt instead.

Changes;

  • Find the old tokens to migrate by scanning the Caskroom instead of going through installed_caskfile, the same way Migrator.oldnames_needing_migration does for formulae, and migrate all of them rather than just the first.
  • When the new cask is already installed under its own token, the old cask is a separate installation that can't be moved to the new token, so uninstall it and leave behind the same Caskroom/<old_token> -> <new_token> symlink that a migration would. It's loaded from its own installed caskfile so that its own artifacts (Codex.app, not ChatGPT.app) are the ones removed. This is what already happens when only the old cask is installed, just earlier: today the migration moves the old staged payload under the new token, and the next brew upgrade uninstalls the old artifacts before installing the new ones.
  • Deduplicate Cask::Caskroom.casks so that an old token which hasn't been migrated yet can't list the same cask twice while it's still there.
  • Report errors per old token so that one failure can't abort brew update.
  • Don't uninstall artifacts when they are shared between the two casks. Alternatively, we could have the migration always run uninstall --> install on migration, in the same way brew upgrade does.

  • Have you followed our Contributing guidelines?
  • Have you checked for other open Pull Requests for the same change?
  • Have you explained what your changes do? Performance claims (e.g. "this is faster") must include Hyperfine benchmarks.
  • Have you explained why you'd like these changes included, not just what they do?
  • For bug fixes, have you given step-by-step brew commands to reproduce the bug?
  • Have you written new tests (excluding integration tests)? Here's an example.
  • Have you successfully run brew lgtm (style, typechecking and tests) locally?

  • AI was used to generate or assist with generating this PR.

Claude Opus 4.8 Max was used to debug the issue and implement the fix.

`Cask::Migrator.migrate_if_needed` determined the token to migrate from
the new cask's installed caskfile, but that lookup checks the new token
before any old tokens. When both the old and the new cask were installed,
it always found the new cask's own caskfile, decided nothing needed
migrating and left the old cask's Caskroom directory in place. That
directory then loaded as the cask it was renamed to, so the same cask was
listed, reported as outdated and upgraded twice.

Instead, migrate every old token that still has its own Caskroom
directory. When the new cask is already installed under its own token the
old cask is a separate installation that cannot be moved to the new
token, so uninstall it (loaded from its own installed caskfile, so that
its own artifacts are the ones removed) and leave behind the same symlink
that a migration would.

Casks that are renamed into each other can share artifacts, so skip any
artifact that the new cask installs too: uninstalling those would remove
them from the new cask, which stays installed.

Also deduplicate `Cask::Caskroom.casks` so that an old token which has
not been migrated yet cannot list the same cask twice.
Copilot AI review requested due to automatic review settings July 13, 2026 04:27
@bevanjkay bevanjkay force-pushed the cask-migrator-both-installed branch from a4bc8e6 to 3405c57 Compare July 13, 2026 04:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes cask migration when a cask is renamed to a token that is already installed, ensuring the old-token install is correctly handled (including safe artifact removal) and preventing duplicate listing of the renamed cask while it still exists on disk.

Changes:

  • Update Cask::Migrator to discover all installed old tokens from Caskroom state, and to uninstall (not move) the old-token install when the new token is already installed, while preserving shared artifacts.
  • Add support in Cask::Installer to restrict which artifacts are uninstalled (used to avoid removing artifacts owned by the still-installed new cask).
  • Deduplicate Cask::Caskroom.casks by resolved cask identity and add coverage for both migration and listing behavior (including shared-artifact scenarios).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Library/Homebrew/cask/migrator.rb Reworks migration discovery and behavior for “new token already installed” cases; adds shared-artifact-aware uninstall path.
Library/Homebrew/cask/installer.rb Adds default_uninstall_artifacts: to restrict uninstall to a specific artifact subset; prevents overwrite when already set.
Library/Homebrew/cask/caskroom.rb Deduplicates installed cask enumeration to avoid listing the same resolved cask twice.
Library/Homebrew/test/cask/migrator_spec.rb Adds unit/spec coverage for old-token discovery, uninstall-vs-move behavior, dry-run behavior, and shared-artifact handling.
Library/Homebrew/test/cask/caskroom_spec.rb Adds regression test ensuring renamed-but-not-migrated installs don’t cause duplicate listing.
Library/Homebrew/test/support/fixtures/cask/Casks/local-caffeine-clone.rb Adds fixture cask to test shared-artifact migration/uninstall behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Library/Homebrew/cask/migrator.rb
Comment thread Library/Homebrew/cask/migrator.rb
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@MikeMcQuaid MikeMcQuaid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@MikeMcQuaid MikeMcQuaid added this pull request to the merge queue Jul 13, 2026
Merged via the queue into main with commit b2d0223 Jul 13, 2026
41 checks passed
@MikeMcQuaid MikeMcQuaid deleted the cask-migrator-both-installed branch July 13, 2026 07:28
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.

3 participants