After running brew upgrade on my Mac M3, OfflineIMAP suddenly stopped working with this error:

OfflineIMAP 8.0.1
  Licensed under the GNU GPL v2 or any later version (with an OpenSSL exception)
imaplib2 v3.06, Python v3.14.0, OpenSSL 3.6.0 1 Oct 2025
Account sync Peter:
 *** Processing account Peter
 Establishing connection to imap.mailbox.org:993 (PeterRemote)
 ERROR: While attempting to sync account 'Peter'
  No module named 'keyring'
 *** Finished account 'Peter' in 0:00
ERROR: Exceptions occurred during the run!
ERROR: While attempting to sync account 'Peter'
  ModuleNotFoundError: No module named 'keyring'

For context, OfflineIMAP had been working perfectly for a long time. I never needed any keyring module - it would simply prompt me for passwords when needed. The brew upgrade likely updated either OfflineIMAP or Python 3.14, breaking this dependency.

Understanding the Root Cause#

The issue occurs because:

  1. Homebrew upgraded OfflineIMAP or Python to a newer version
  2. The new version has an implicit dependency on the keyring Python module
  3. Homebrew's OfflineIMAP formula doesn't declare keyring as a dependency
  4. The module is missing from the Python environment that OfflineIMAP uses

When I checked which Python OfflineIMAP was using:

which offlineimap
head -1 (which offlineimap)

It pointed to /opt/homebrew/opt/[email protected] - Homebrew's managed Python installation.

System-wide Installation#

There are two ways to install the missing keyring module - the first is system-wide:

/opt/homebrew/opt/[email protected]/bin/pip3 install keyring --break-system-packages

This installs keyring directly into Homebrew's Python 3.14 site-packages directory at /opt/homebrew/lib/python3.14/site-packages/.

Collecting keyring
  Downloading keyring-25.7.0-py3-none-any.whl.metadata (21 kB)
Collecting jaraco.classes (from keyring)
  Downloading jaraco.classes-3.4.0-py3-none-any.whl.metadata (2.9 kB)
...
Successfully installed jaraco.classes-3.4.0 jaraco.context-6.0.1 jaraco.functools-4.1.0 keyring-25.7.0 more-itertools-10.5.0

The second option is an user installation by adding --user switch:

/opt/homebrew/opt/[email protected]/bin/pip3 install keyring --break-system-packages --user

This installs keyring into your user's local Python packages directory at ~/.local/lib/python3.14/site-packages/.

For single-user Mac systems (most common case), Option 1 is simpler and more straightforward. For multi-user systems or if you want packages to survive Python upgrades, Option 2 is better.

Understanding --break-system-packages#

The --break-system-packages flag is required because of PEP 668, which prevents pip from modifying system-managed Python installations. Modern Python distributions (including Homebrew's) are marked as "externally managed" to prevent conflicts between pip and the system package manager.

While the flag name sounds dangerous, on macOS with Homebrew it's relatively safe because:

  • Homebrew's Python is isolated from macOS system Python (/usr/bin/python3)
  • Breaking Homebrew's Python won't affect macOS system tools
  • You can always recover with brew reinstall [email protected]

Verification#

After installing keyring, OfflineIMAP works perfectly:

offlineimap

The error is gone, and email synchronization proceeds normally.

Why This Happened#

Looking at Homebrew's OfflineIMAP formula, the keyring module is not listed as a resource dependency:

resource "distro" do
  url "https://files.pythonhosted.org/packages/.../distro-1.9.0.tar.gz"
  sha256 "2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"
end

resource "imaplib2" do
  # ...
end

resource "rfc6555" do
  # ...
end

resource "urllib3" do
  # ...
end

# keyring is missing!

This appears to be an oversight in the Homebrew formula. The proper fix would be to submit a pull request to homebrew-core adding keyring and its dependencies (jaraco.classes, jaraco.context, jaraco.functools, more-itertools) to the formula.

Conclusion#

The --break-system-packages approach is a pragmatic fix for this dependency gap in Homebrew's OfflineIMAP formula. While it's not the most elegant solution, it works reliably and can be easily repeated if needed after Python upgrades. Enjoy!