uninstall.sh deletes ALL symlinks under the prefix, not just broken ones (/usr/bin/test does not exist on macOS)
Summary
uninstall.sh detects broken symlinks with -exec /usr/bin/test -e '{}' ';'.
/usr/bin/test does not exist on macOS — test lives at /bin/test.
When find cannot execute the binary, the -exec returns non-zero, the leading !
inverts that to true, and every symlink matches. The script then runs -delete
on all of them — and if that find returns non-zero (e.g. any Permission denied
during traversal), it retries the same delete as root:
args=("${HOMEBREW_PREFIX}" -type l ! -exec /usr/bin/test -e '{}' ';') # line 506
...
args+=(-delete)
/usr/bin/find "${args[@]}" &>/dev/null || execute_sudo /usr/bin/find "${args[@]}"
Minimal reproduction
mkdir -p linktest/real && touch linktest/real/file
ln -s "$PWD/linktest/real/file" linktest/good.link
ln -s "$PWD/linktest/nonexistent" linktest/bad.link
# current code — matches BOTH (wrong)
find linktest -type l ! -exec /usr/bin/test -e '{}' ';' -print
# linktest/good.link
# linktest/bad.link
# (plus: find: /usr/bin/test: No such file or directory)
# with /bin/test — matches only the broken one (correct)
find linktest -type l ! -exec /bin/test -e '{}' ';' -print
# linktest/bad.link
Why this has gone unnoticed
For a default-prefix uninstall (/opt/homebrew, Linuxbrew) the prefix is being
deleted wholesale anyway, so over-matching symlinks inside it is harmless.
It only causes damage when the prefix contains non-Homebrew files — i.e. the
macOS Intel prefix /usr/local, which is shared with Node, MySQL, PKCS#11 drivers,
the standalone AWS CLI, and anything else that installs there.
Real-world impact
Uninstalling a /usr/local install on macOS 26.6.2 (Apple Silicon, migrating to
/opt/homebrew), the dry run listed 57,339 paths. Measured on that system:
|
count |
total symlinks under /usr/local |
28,628 |
| matched by the current code |
28,628 |
actually broken (/bin/test) |
3 |
Among the 28,625 valid symlinks it would have deleted as root:
/usr/local/lib/pkcs11/libeTPkcs11.dylib and two siblings — PKCS#11 smart-card
driver registrations installed by a third-party vendor package
/usr/local/bin/npm
- 62 entries under
/usr/local/lib/node_modules
With the one-word patch below, the same dry run drops from 57,339 lines to 86, and
the broken-symlink section correctly lists only the 3 genuinely dangling links.
Suggested fix
- args=("${HOMEBREW_PREFIX}" -type l ! -exec /usr/bin/test -e '{}' ';')
+ args=("${HOMEBREW_PREFIX}" -type l ! -exec /bin/test -e '{}' ';')
/bin/test exists on both macOS and Linux, so this is portable.
Note -xtype l is not a viable alternative: BSD find on macOS rejects it
(find: -xtype: unknown primary or operator).
Environment
- macOS 26.6.2 (arm64, Apple M5 Pro)
- Homebrew 6.0.20
uninstall.sh at HEAD, line 506
ls /usr/bin/test → No such file or directory; /bin/test present
uninstall.sh deletes ALL symlinks under the prefix, not just broken ones (
/usr/bin/testdoes not exist on macOS)Summary
uninstall.shdetects broken symlinks with-exec /usr/bin/test -e '{}' ';'./usr/bin/testdoes not exist on macOS —testlives at/bin/test.When
findcannot execute the binary, the-execreturns non-zero, the leading!inverts that to true, and every symlink matches. The script then runs
-deleteon all of them — and if that find returns non-zero (e.g. any
Permission deniedduring traversal), it retries the same delete as root:
Minimal reproduction
Why this has gone unnoticed
For a default-prefix uninstall (
/opt/homebrew, Linuxbrew) the prefix is beingdeleted wholesale anyway, so over-matching symlinks inside it is harmless.
It only causes damage when the prefix contains non-Homebrew files — i.e. the
macOS Intel prefix
/usr/local, which is shared with Node, MySQL, PKCS#11 drivers,the standalone AWS CLI, and anything else that installs there.
Real-world impact
Uninstalling a
/usr/localinstall on macOS 26.6.2 (Apple Silicon, migrating to/opt/homebrew), the dry run listed 57,339 paths. Measured on that system:/usr/local/bin/test)Among the 28,625 valid symlinks it would have deleted as root:
/usr/local/lib/pkcs11/libeTPkcs11.dyliband two siblings — PKCS#11 smart-carddriver registrations installed by a third-party vendor package
/usr/local/bin/npm/usr/local/lib/node_modulesWith the one-word patch below, the same dry run drops from 57,339 lines to 86, and
the broken-symlink section correctly lists only the 3 genuinely dangling links.
Suggested fix
/bin/testexists on both macOS and Linux, so this is portable.Note
-xtype lis not a viable alternative: BSDfindon macOS rejects it(
find: -xtype: unknown primary or operator).Environment
uninstall.shat HEAD, line 506ls /usr/bin/test→ No such file or directory;/bin/testpresent