Skip to content

cleanup: remove old kegs of outdated formulae - #24465

Merged
MikeMcQuaid merged 5 commits into
mainfrom
cleanup-keep-newest-keg
Nov 4, 2026
Merged

MikeMcQuaid merged 5 commits into
mainfrom
cleanup-keep-newest-keg

Conversation

@HaraldNordgren

@HaraldNordgren HaraldNordgren commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

brew cleanup prints Warning: Skipping <formula>: most recent version <version> not installed for every outdated formula and cleans up nothing for them. Most of these formulae have a single keg, so there is nothing to clean anyway. The comment above the warning still says "If the rack only has one version installed, don't complain" and the original check was rack.children.length > 1, but cc8859f ("cleanup: use installed_prefixes/installed_kegs") turned it into installed_prefixes.any? in 2015.

When several kegs are installed but not the latest, brew cleanup keeps all of them and warns that it can't tell which one to keep. It can. With the latest installed, cleanup already picks the keg to keep by version alone, linked or not, and only spares older kegs that are linked, pinned or still needed by another keg. On my install this left kegs like pcre 8.40, 8.41 and 8.42 next to 8.44.

When the latest version isn't installed, Formula#eligible_kegs_for_cleanup now keeps the highest installed stable keg, by version_scheme and then version, and removes the older ones with the same linked, pinned and keepme protections. HEAD kegs are left alone, as the latest-installed path does when a stable spec is active. So are incomplete kegs without an install receipt, so a keg left behind by a killed install is never kept in place of a complete one. The warning now reads Skipping <keg>: most recent version <version> not installed and only prints when older kegs are being removed. A formula with a single keg stays quiet, pinned formulae stay quiet as before and the message stops once the older kegs are gone.

The version_scheme ordering matters for downgrades like openssl@3 3.6.5 to 3.5.9 (LTS), which bumped version_scheme to 1. With 3.6.2, 3.6.3 and 3.6.5 installed, cleanup keeps 3.6.5. Applying the latest-installed rule instead would treat all three as older than 3.5.9 and remove every keg, which one of the new specs checks.

To reproduce, with outdated formulae that have one or several old kegs installed:

$ ls "$(brew --cellar)/pcre"
8.40	8.41	8.42	8.44
$ ls "$(brew --cellar)/brotli"
1.0.9
$ brew cleanup -n pcre openssl@3 brotli
Warning: Skipping pcre: most recent version 8.45 not installed
Warning: Skipping openssl@3: most recent version 3.5.9 not installed
Warning: Skipping brotli: most recent version 1.2.1 not installed

  • 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 brew benchmark results.
  • 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?

  • I did not use AI/LLM to create this PR, or I disclosed the tool/model below and reviewed its output; I did not attribute commits to AI and will answer maintainer questions and review comments myself without AI/LLM.

Claude Code (Claude Opus 5.5) traced the warning back to cc8859f, wrote the change and its specs and verified them. Each new spec fails against main or a deliberately broken copy of the change and passes with it. It ran brew lgtm, which passes. The reproduction output above is from my install on main.


@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

Summary

Summary by CodeRabbit

  • Bug Fixes
    • Cleanup can select eligible installations when multiple prefixes are installed, even if the latest version is absent.
    • When the latest version is absent, cleanup excludes HEAD installations and keeps the newest eligible installation. Older installations may trigger a warning unless the formula is quiet or pinned.
    • Existing cleanup behavior is preserved when the latest version is installed, including selection between HEAD and stable installations when a latest HEAD version is available.

eligible_kegs_for_cleanup now selects older installed kegs when the latest formula version is absent. It retains existing exclusions and removes the former warning. Tests cover linked kegs, version schemes, downgrade histories, HEAD kegs, and incomplete kegs.

Changes: Formula keg cleanup

Layer / File(s) Summary
Cleanup eligibility and regression coverage
Library/Homebrew/formula.rb, Library/Homebrew/test/formula_spec.rb
Cleanup eligibility now requires multiple installed prefixes. If the latest version is absent, the method selects all but the newest eligible non-HEAD keg, ordered by scheme and version. Existing linked-keg exclusions remain. The former warning is removed. Tests cover version ordering, downgrade histories, HEAD kegs, and incomplete kegs.
Merge Risk: Low · up to fbd98

Cleanup can warn that it is keeping a keg even when it removes nothing. This is a bounded messaging issue that can be corrected with a localized change.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: removing old kegs for outdated formulae during cleanup.
Description check Passed The description directly explains the cleanup behaviour, warning changes, retention rules, protections, tests, and reproduction steps.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Comment thread Library/Homebrew/formula.rb Resolved
Comment thread Library/Homebrew/formula.rb Resolved
Comment thread Library/Homebrew/formula.rb Resolved
@HaraldNordgren HaraldNordgren changed the title cleanup: keep newest keg if latest isn't installed cleanup: remove old kegs of outdated formulae Nov 3, 2026

@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.

Code looks good, one question.

Comment thread Library/Homebrew/formula.rb Resolved
- When the latest version wasn't installed, `brew cleanup` removed
  nothing and warned `Skipping <formula>: most recent version <version>
  not installed`, although the older versions are unused.
- `cc8859f86a` made this warn for every outdated formula, even with a
  single keg and nothing to clean.
- Keep the highest installed version instead and remove older ones,
  with the same linked, pinned and `keepme` protections.
- With a stable keg active and the formula's stable version moved on,
  HEAD kegs were sorted together with stable ones. HEAD versions
  compare equal, so an arbitrary HEAD keg was kept and the active
  stable keg was removed whenever only `opt` pointed to it.
- Leave HEAD kegs alone here, as the latest-installed path does when
  a stable spec is active.
- An install that is killed hard can leave an empty or partial keg.
  When it was the highest version, cleanup kept it and removed the
  only complete keg.
- Only consider kegs with an install receipt, as
  `any_version_installed?` does, so incomplete kegs are neither kept
  nor removed.
- Share a `setup_keg` spec helper that writes the receipt.
- Keep telling users that the latest version isn't installed, but
  only when cleanup picks a keg to keep. A formula with a single keg
  stays quiet and the message stops once the older kegs are gone.
- `Skipping` is no longer true here, so name the kept keg instead.
- Stay quiet for pinned formulae, as before.
@HaraldNordgren
HaraldNordgren force-pushed the cleanup-keep-newest-keg branch from 1da4561 to f1a10e4 Compare November 4, 2026 10:02
- Nearby messages use `Skipping <keg>` for a keg that isn't removed,
  so match them and stay closer to the original message.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

Minor · Emit the warning only when a cleanup candidate remains. · formula.rb:3838

Library/Homebrew/formula.rb:3838
Minor · Functional Correctness · Quick win

Emit the warning only when a cleanup candidate remains.

When every older keg is linked or protected by keepme, line 3838 emits the missing-latest warning before protection filtering. eligible_kegs_for_cleanup then returns no keg, so cleanup_formula performs no removal. Move the warning below the protection loop and check the post-protection list. Keep the newest_keg guard so other cleanup branches do not use this warning.

Suggested fix
         newest_keg = kegs.pop
-        if !kegs.empty? && !quiet && !pinned?
-          opoo "Skipping #{newest_keg}: most recent version #{pkg_version} not installed"
-        end
         kegs
@@
       unless eligible_kegs.empty?
         eligible_kegs.each do |keg|
           if keg.linked?
             opoo "Skipping (old) #{keg} due to it being linked" unless quiet
           elsif pinned? && keg == Keg.new(resolved_path(@pin.path))
             opoo "Skipping (old) #{keg} due to it being pinned" unless quiet
           elsif (keepme_refs = keg.keepme_refs.presence)
             opoo "Skipping #{keg} as it is needed by #{keepme_refs.join(", ")}" unless quiet
           else
             eligible_for_cleanup << keg
           end
         end
       end
+      if newest_keg && !eligible_for_cleanup.empty? && !quiet && !pinned?
+        opoo "Skipping #{newest_keg}: most recent version #{pkg_version} not installed"
+      end
Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0d95ee2f-f5ec-4309-b7b4-0d1a22787a1f
Commits

Reviewing files that changed from the base of the PR and between f1a10e4 and fbd986e.

Files selected for processing: 2
  • Library/Homebrew/formula.rb
  • Library/Homebrew/test/formula_spec.rb
Files skipped from review as they are similar to previous changes: 2
  • Library/Homebrew/formula.rb
  • Library/Homebrew/test/formula_spec.rb

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

@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 enabled auto-merge November 4, 2026 11:26
@MikeMcQuaid
MikeMcQuaid disabled auto-merge November 4, 2026 11:37
@MikeMcQuaid
MikeMcQuaid added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit acfb136 Nov 4, 2026
55 checks passed
@MikeMcQuaid
MikeMcQuaid deleted the cleanup-keep-newest-keg branch November 4, 2026 11:59
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.

2 participants

⌂ 换网址