-
-
Notifications
You must be signed in to change notification settings - Fork 10.9k
Description
brew doctor output
Warning: Some installed casks are deprecated or disabled.
You should find replacements for the following casks:
chromedriver
inkscape
prowlarr
radarr
sonarrVerification
- I ran
brew updatetwice and am still able to reproduce my issue. - My "
brew doctoroutput" above saysYour system is ready to brewor a definitely unrelatedTiermessage. - This issue's title and/or description do not reference a single formula e.g.
brew install wget. If they do, open an issue at https://github.com/Homebrew/homebrew-core/issues/new/choose instead.
brew config output
HOMEBREW_VERSION: 5.0.8-15-g7543440
ORIGIN: https://github.com/Homebrew/brew
HEAD: 754344022ff6b44f759c04b828fed0e9a76e3216
Last commit: 35 hours ago
Branch: main
Core tap JSON: 02 Jan 00:33 UTC
Core cask tap JSON: 02 Jan 00:33 UTC
HOMEBREW_PREFIX: /opt/homebrew
HOMEBREW_CASK_OPTS: []
HOMEBREW_DOWNLOAD_CONCURRENCY: 20
HOMEBREW_FORBID_PACKAGES_FROM_PATHS: set
HOMEBREW_MAKE_JOBS: 10
HOMEBREW_SORBET_RUNTIME: set
Homebrew Ruby: 3.4.8 => /opt/homebrew/Library/Homebrew/vendor/portable-ruby/3.4.8/bin/ruby
CPU: deca-core 64-bit arm_firestorm_icestorm
Clang: 17.0.0 build 1700
Git: 2.50.1 => /Library/Developer/CommandLineTools/usr/bin/git
Curl: 8.7.1 => /usr/bin/curl
macOS: 26.2-arm64
CLT: 26.2.0.0.1.1764812424
Xcode: N/A
Rosetta 2: falseWhat were you trying to do (and why)?
I'm using brew bundle --cleanup to keep my system in sync with a Brewfile (declarative package management). The --cleanup flag should remove packages not in the Brewfile while preserving packages that are in the Brewfile.
What happened (include all command output)?
Packages explicitly listed in my Brewfile were removed by brew bundle --cleanup.
Specifically, tree was removed even though it was in my Brewfile:
brew "tree"
After investigation, I found this happens when:
- A package is explicitly in the Brewfile
- That package has
installed_on_request: false(was originally installed as a dependency) - The package it was a dependency of is not in the Brewfile
The --cleanup operation removes the "parent" package, which triggers autoremove. The autoremove process doesn't know about the Brewfile, so it removes packages that appear to be orphaned dependencies, even if they're explicitly listed in the Brewfile.
What did you expect to happen?
Packages explicitly listed in the Brewfile should not be removed by brew bundle --cleanup, regardless of how they were originally installed (on request vs as dependency).
Step-by-step reproduction instructions (by running brew commands)
# 1. Install a package that has dependencies
brew install yt-dlp
# 2. Verify deno was installed as a dependency
brew info --json=v2 deno | jq '.formulae[0].installed[0].installed_on_request'
# Returns: false
# 3. Verify deno is only used by yt-dlp
brew uses --installed deno
# Returns: yt-dlp
# 4. Create a test Brewfile that has deno but not yt-dlp
echo 'brew "deno"' > /tmp/test-brewfile
# 5. Run cleanup dry-run to see what would be removed
brew bundle cleanup --file=/tmp/test-brewfile
# Output shows "Would uninstall formulae: yt-dlp" (correct - not in Brewfile)
# Note: deno does NOT appear here because the dry-run only shows direct removals,
# not the cascade effect of autoremove that happens afterward.
# 6. TRIGGER THE BUG - run with --force
# WARNING: This will uninstall everything not in the test Brewfile!
# Only run this if you're prepared to reinstall your packages afterward.
brew bundle cleanup --file=/tmp/test-brewfile --force
# Output: "Uninstalled 1 formula" (yt-dlp)
# But then autoremove kicks in and silently removes deno too!
# 7. Verify deno was incorrectly removed
brew list deno
# Error: No such keg: /opt/homebrew/Cellar/deno
# BUG: deno was in the Brewfile but got removed anyway!Why the dry-run is misleading: The dry-run only shows what brew bundle cleanup will directly uninstall. It cannot predict what autoremove will do afterward. The bug is a cascade effect: removing yt-dlp makes deno appear "orphaned" to autoremove, which then removes it despite being in the Brewfile.
Note: The bug also affects brew bundle --cleanup and brew bundle install --cleanup because these flags run cleanup with force: true hardcoded (no dry-run).
Root Cause Analysis
The issue is in the interaction between brew bundle cleanup and Homebrew's autoremove:
brew bundle --cleanupcallsbrew uninstallfor packages not in Brewfile- Uninstalling triggers Homebrew's install cleanup (autoremove)
autoremovelooks atinstalled_on_requestflag to find orphansautoremovehas no knowledge of the Brewfile- Packages with
installed_on_request: falseare removed even if in Brewfile
Workaround
HOMEBREW_NO_INSTALL_CLEANUP=1 brew bundle --cleanup --file=~/.BrewfileSuggested Fix
Option 1: brew bundle sets HOMEBREW_NO_AUTOREMOVE=1 during cleanup operations
- Pros: Simple, self-contained change in bundle code
- Cons: Disables autoremove entirely, so genuinely orphaned dependencies (not in Brewfile) won't be cleaned up either
Option 2: brew bundle marks Brewfile packages as installed_on_request: true before running cleanup
- Pros: Uses existing Homebrew mechanism
- Cons: Persists metadata changes to disk, which is a side effect cleanup shouldn't have
Option 3: autoremove accepts a "protected packages" list that brew bundle can pass
- Pros: Surgical fix that lets autoremove still clean up truly orphaned packages while respecting the Brewfile
- Cons: Requires changes to core autoremove logic, not just bundle
Option 3 is the cleanest solution as it addresses the root cause: autoremove has no knowledge of the Brewfile. By passing the Brewfile packages as a protected list, autoremove can make informed decisions rather than being disabled entirely.