Skip to content

uninstall.sh deletes ALL symlinks under the prefix, not just broken ones (/usr/bin/test does not exist on macOS) #1136

Description

@Valentin-Iliev

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 macOStest 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions