Zsh brew completion - feature, bug, or external conflict?


I am running Homebrew with zsh / oh-my-zsh. When I use tab completions with brew, I end up with what looks like a double completion set—one which is a little more informative and only contains primary commands, and another below it which is a list of primary commands and aliases:

$ brew in⇥
info     -- information about a formula
install  -- install a formula
info     instal   install

I am wondering if this is the intended behavior, or if I might have a secondary zsh completion configuration competing with the Homebrew built-in?

Also, the particularly-annoying instal and uninstal aliases impact command line completion, such that typing brew ins⇥ results in brew instal rather than brew install␣ (with trailing space), meaning you have to hit l⇥ or the space bar before inputting the formula name.

Checking my $fpath yields:

$ echo $fpath | tr " " "\n"
 * * several other oh-my-zsh plugins * *

The only location containing a _brew file is /usr/local/share/zsh/site-functions, which is the Homebrew-installed symlink to ../../../Homebrew/completions/zsh/_brew. So I feel like this is coming from Homebrew’s completions/zsh/_brew file, but I’m far from an expert on this topic, and apparently there’s some caching involved in zshcompsys, so ¯\_(ツ)_/¯. (I have tried the recommended rm -f ~/.zcompdump; compinit to refresh the completions cache, but to no avail.)

I have probably previously installed and subsequently uninstalled homebrew-core/zsh-completions, but it’s not currently installed.

There is a merged PR removing the instal and uninstal bash completions, but I don’t see anything similar for the zsh completions.

If this isn’t Homebrew-related, hopefully someone can point me in the right direction to track down the source.

Conversely, if this is all coming from Homebrew’s _brew completion file, I’d like to suggest removing instal and uninstal command line completion and the bottom command-only tab completion selections, leaving just the detailed options. For comparison, here’s what git provides:

$ git pu⇥
pull  -- fetch from and merge with another repository or a local branch
push  -- update remote refs along with associated objects

I’m happy to create an issue for this, but I wanted to get confirmation first that it wasn’t something else on my system other than Homebrew that was causing it. (I’d love to help with a PR, but I took a look at _brew alongside the bash completion diff from the PR linked above, and I have no earthly idea where to begin.)

(dana) #2

They are included in Homebrew’s _brew; the command here is what produces them.

What it’s doing is breaking the commands up into ‘common commands’ (which include the canonical names) and ‘all commands’ (which include the aliases). This would make more sense for you visually if you enabled grouping and group descriptions in the completion output:

# Put these in your .zshrc if you like them; otherwise, they will go away
# when you restart the shell
% zstyle ':completion:*' group-name ''
% zstyle ':completion:*:descriptions' format 'completing %d:'

% brew in<tab>
completing common commands:
info     -- information about a formula
install  -- install a formula
completing all commands:
info     instal   install

I think i agree with you that it isn’t necessary to include the aliases that are one letter short of the canonical names — it’s just redundant noise. I had planned to do some more work on Homebrew’s completion function, so i can submit a PR to remove these from the zsh function too if nobody beats me to it.

If it really irritates you in the mean time, or just generally for your information i guess, you can configure zsh to ignore possibilities you don’t want to see. In this case you might do:

zstyle ':completion:*:*:brew(-*|):*:all-commands' ignored-patterns \
  instal uninstal


Thanks so much for this. I appreciate your PR fixing the issue.

For anyone else having trouble with zsh completions, I wanted to add some additional info that applied to my configuration and might also apply to yours:

I discovered that my zsh completions cache was not located in ~/.zcompdump but rather in ~/.oh-my-zsh/cache. So rather than rm -rf ~/.zcompdump to clear the cache, I ended up having to delete ~/.oh-my-zsh/cache/brew_all_commands (since I didn’t want to just completely nuke the directory—there seems to be some non-completion-related stuff in there, like oh-my-zsh’s last update timestamp).

The next time I typed brew ⇥, it took a few seconds and rebuilt the cache file. However, brew instal was still there. So, in an attempt to reset things, I again deleted the cache file and invoked a fresh shell by issuing zsh. Boom. Worked great.

Strangely, the next time I opened a fresh terminal session, typing brew ins⇥ did in fact complete to brew install but there was no trailing space like there was the first time I got things working. So again, I opened a new shell with zsh and now brew ins⇥ expanded to brew install␣ (with trailing space). Strange.

Troubleshooting this, I checked my $fpath in both sessions, and I was a little surprised to find that it was as I posted above in a fresh terminal session, but that in a zsh-invoked session it had changed to:

# * * several other oh-my-zsh plugins * *

So in a fresh terminal shell, I was getting site-functions from both the macOS system zsh and Homebrew’s, while functions was only the system version and not Homebrew’s. At first, I thought I might somehow be in the system zsh when starting a new terminal, but zsh --version did indeed confirm that I was always running Homebrew’s 5.7.1.

After some Googling around, I found this page and which led me to check:

$ dscl . -read /Users/$USER UserShell
UserShell: /bin/zsh

So something was crossed up. Even though I was inside the Homebrew zsh, macOS’s pointers were suggesting the system zsh. Following the procedure at the above site, I tried:

$ sudo dscl . -create /Users/$USER UserShell /usr/local/bin/zsh

which did the trick. Now $fpath and zsh --version and which zsh and dscl . -read /Users/$USER UserShell were all finally pointing to the same /usr/local/bin/zsh from Homebrew’s install, and I was getting the coveted trailing space in my brew command completions, whether it was a fresh shell or a newly-invoked nested zsh session.

As an added bonus, another problem I had been having also seems to have been resolved. Homebrew’s formula list was finally being cached again, so I am now back to getting instant suggestions instead of waiting on a network request every time when tab-completing formulae names.

I am not sure whether this is just something particular to my installation, or whether a caveats section should be added to the homebrew-core/Formula/zsh.rb formula suggesting the above command if the user wishes to make Homebrew’s zsh the default shell for new sessions?

(dana) #4

Yeah, the first fpath you posted definitely suggests you were using the system zsh before. Your path has /usr/local/... ahead of the other stuff, so when you ran zsh again you’d get the Homebrew one. There are a few ways to set your default shell; the classic UNIX method is the chsh command. You can also change it just for Terminal (or iTerm, or whatever) in its own preferences.

About /usr/local/share/zsh/site-functions, that’s not Homebrew-specific — it’s in zsh’s default fpath, which Apple ships with. However, because Homebrew kind of takes over /usr/local for itself, on systems where it’s being used you may find functions installed by it there.

Lastly, zsh --version only tells you the version of the zsh that’s first in your path, which may or may not be the one you’re actually running. To get the version of the current zsh, use echo $ZSH_VERSION.


I was just about to edit my post to correct this error, but you beat me to it. That’s definitely what was happening.

I’m going to see if I can figure out what the “official” macOS method of default shell selection is for the last few versions and cobble together a caveats section for the zsh formula (and I guess eventually the other Homebrew shell formulae as well) to inform users what steps to take if they really want to switch their default shell. In my case, I think I had been using the Homebrew zsh with Terminal via explicitly calling /usr/local/bin/zsh in its preferences, but when I switched to iTerm a couple of years ago it reverted not to bash (which would’ve been obvious) but to reading the system zsh from that preference location. To think all this time I’ve actually been using the system zsh (or some bastardization of the two) without knowing it.