Homebrew doesn’t actually check if an upgrade will break your installed software.

Let’s say you’ve got a Python project that absolutely needs Python 3.9. You run brew upgrade python. Homebrew, being the eager beaver it is, happily installs Python 3.12. Your project? It now fails to run because it’s looking for 3.9, and the system’s default python command now points to 3.12. This is a common, albeit frustrating, scenario. Homebrew’s core philosophy is to get you the latest versions, not to maintain strict backward compatibility for your specific application stack.

Here’s how we can navigate this and keep your Homebrew packages in a sane state:

The "Safe Upgrade" Workflow

The key is to understand what’s about to change before you commit to it.

  1. See What’s Outdated: Before anything, let’s see which packages have newer versions available.

    brew outdated
    

    This command lists all installed packages for which a newer version exists in Homebrew’s repositories. It’s your "potential trouble" list.

  2. Inspect a Specific Package: If you see a critical package like python or node in the brew outdated list, you’ll want to know what version Homebrew wants to install.

    brew info python
    

    Look for the line that shows the latest available version. For example, you might see 3.12.1 (latest). This tells you that brew upgrade python would install 3.12.1.

  3. Check Dependencies: Sometimes, upgrading one package will pull in newer versions of its dependencies, which can also cause issues.

    brew dependency python
    

    This will show you what python relies on. If any of those dependencies are also outdated, brew upgrade python might implicitly upgrade them too. You can check the info for those dependencies as well.

  4. Upgrade One by One (The Safest Bet): For critical packages, avoid brew upgrade. Instead, upgrade them individually.

    brew upgrade python@3.9
    

    If you specifically want to upgrade to python@3.9 (which might be a specific versioned formula), this is how you’d target it. If you just want the latest python, you’d typically run brew upgrade python. However, if you need a specific version, you might need to install it explicitly, e.g., brew install python@3.10 and then manage your PATH or symlinks. Homebrew’s versioned formulae (like python@3.9) are designed to coexist.

  5. The "Dry Run" (Sort Of): Homebrew doesn’t have a perfect dry-run for upgrades that shows exactly what will be installed and how it affects your system. However, brew upgrade --verbose can sometimes give more insight, but it’s often too noisy. The best "dry run" is manually checking brew info for each package you’re considering upgrading and then examining brew outdated.

  6. Pinning a Package (To Prevent Accidental Upgrades): If you have a package that must not be upgraded, you can "pin" it.

    brew pin python@3.9
    

    Now, if you were to run brew upgrade, python@3.9 would be skipped. To unpin it later:

    brew unpin python@3.9
    

    This is crucial for environments where specific versions are required.

  7. The Standard Upgrade (When You’re Ready): When you’re confident that the upgrades won’t break your workflow, or for less critical packages, you can proceed with a broader upgrade.

    brew upgrade
    

    This command upgrades all outdated packages. If you’ve pinned packages, they will be skipped.

  8. Cleaning Up Old Versions: After upgrades, Homebrew keeps old versions around for a while. You can clean them up to free disk space.

    brew cleanup
    

    This removes old versions of all formulae, leaving only the currently installed ones.

A Real-World Scenario: Node.js

Let’s say you’re developing a web app that uses a specific Node.js version, e.g., Node 18.

  1. You run brew outdated. You see node in the list, with node 20.x.x available.
  2. You run brew info node. It confirms 20.x.x is the latest.
  3. You know that upgrading node might break your current project.
  4. You decide to keep your current Node.js. You run brew pin node.
  5. Later, when you need to work on a project that requires Node 20, you can unpin it, upgrade, and then potentially pin the new version or manage multiple Node versions using nvm (Node Version Manager) which integrates with Homebrew.

The next common issue you’ll encounter is dealing with outdated "kegs" (the directories where Homebrew installs package versions) that brew cleanup doesn’t remove because they are currently linked.

Want structured learning?

Take the full Homebrew course →