Zsh-z is a command-line tool that allows you to jump quickly to directories that you have visited frequently or recently -- but most often a combination of the two (a concept known as "frecency"). It works by keeping track of when you go to directories and how much time you spend in them. Based on this data, it predicts where you want to go when you type a partial string. For example, z src might take you to ~/src/zsh. z zsh might also get you there, and z c/z might prove to be even more specific -- it all depends on your habits and how long you have been using Zsh-z to build up a database. After using Zsh-z for a little while, you will get to where you want to be by typing considerably less than you would need to if you were using cd.
Zsh-z is a native Zsh port of rupa/z, a tool written for bash and Zsh that uses embedded awk scripts to do the heavy lifting. rupa/z was my most used command-line tool for a couple of years. I decided to translate it, awk parts and all, into pure Zsh script, to see if by eliminating calls to external tools (awk, sort, date, sed, mv, rm, and chown) and reducing forking through subshells I could make it faster. The performance increase is impressive, particularly on systems where forking is slow, such as Cygwin, MSYS2, and WSL.
There is also a significant stability improvement. Race conditions have always been a problem with rupa/z, and users of that utility occasionally lose their ~/.z databases. By having Zsh-z only use Zsh (rupa/z uses a hybrid shell code standard that works on bash as well), I have been able to implement a zsh/system-based file-locking mechanism. It is now nearly impossible to crash the database.
There are other, smaller improvements which I document below in Other Improvements to the Original Functionality of rupa/z. For instance, tab completions are now sorted by frecency by default rather than alphabetically (the latter behavior can be restored if you like it -- see below).
Zsh-z is a drop-in replacement for rupa/z and will, by default, use the same database (~/.z, or whatever database file you specify), so you can go on using rupa/z when you launch bash.
Zsh-z v2.0
v2.0 is the most significant release in the project's history. It is faster than ever at adding, searching, and listing on modern Zsh; it never makes your prompt wait on database writes -- on any platform; it hardens those writes against corruption and against prying eyes; and it makes tab completion "just work" even under
setopt COMPLETE_ALIASES. See v2.0 in the News below for the full rundown, and Performance for the benchmarks.
Table of Contents
- News
- Installation
- Command Line Options
- Settings
- Case Sensitivity
ZSHZ_UNCOMMONZSHZ_OWNER- Making
--addwork for you - Performance
- Other Improvements to the Original Functionality of
rupa/z - Migrating from Other Tools
COMPLETE_ALIASES
News
v2.0 (August 14, 2026)
Version 2.0 is a major step forward, and these are the changes most worth knowing about:
- Zsh-z is even faster than before. Version 2.0 builds on Zsh-z's past successes in optimizing adding to the datafile -- something it does at virtually every prompt -- by streamlining searching and listing, as well. See Performance for the numbers.
- Database writes never block your prompt -- on any platform. The per-prompt
--addhas long run in the background on most systems, but Cygwin and MSYS2 did the write in the foreground, because backgrounding there cost a wrapper subshell plus a job.--addnow runs as a single disowned job (&!) everywhere: one fork, no wrapper subshell, no job-control noise. On Cygwin and MSYS2 that turns a foreground write whose cost grows with your datafile (~30 ms at 300 entries, ~300 ms at 1,000) into a flat ~10-12 ms fork. Elsewhere it halves the forks per prompt. - Safer, crash-resistant concurrent writes. Writes are now guarded by a dedicated, stable lockfile using
zsh/systemfile locking, with a bounded wait for lock acquisition (the newZSHZ_LOCK_TIMEOUT, default1second). Write errors are handled gracefully, and locks are always released even if a write is interrupted. On Cygwin and MSYS2 a write is also retried briefly if Windows refuses it: a virus scanner or the search indexer that opens the database in the instant between its being written and its being moved into place makes the move fail, which used to lose that one directory silently. Zsh-z now retries the move briefly. - Your database file now has
600permissions -- readable and writable only by you -- so that other users on a shared system cannot read your directory history (#92). On Zsh 5+ this uses the in-processzf_chmodbuiltin; on Zsh 4.3.11 it uses aumask-in-a-subshell technique that avoids the fork-and-exec of an externalchmod. COMPLETE_ALIASESworks automatically in normal setups. Tab completion no longer breaks when you havesetopt COMPLETE_ALIASESenabled. Zsh-z registers the alias automatically on the first Tab press, so the manualcompdefline that earlier versions required is no longer necessary. See below.- Fixed a
can't clobber parameter tmpfderror on some Zsh builds. On certain Zsh builds, every database write could fail withcan't clobber parameter tmpfd containing file descriptor 0, leaving an error at each new prompt. The file descriptor used for the temporary database file is now held in an unset scalar rather than one seeded with0, so the write never trips Zsh's file-descriptor-clobber guard (#81). - A misconfigured database file no longer closes your shell -- or nags you at every prompt. When
ZSHZ_DATApoints at a directory, or names a file without a directory, Zsh-z now reports the problem and returns instead of callingexit. The per-prompt--addstays quiet about it, so you are told once, when you actually runz, rather than at every prompt (#103; props @ahjota). z -xcan now remove the entry for a directory that no longer exists -- and can no longer crash Zsh 4.3.11. The removal target used to have to exist on disk, so a database entry whose directory had been deleted -- exactly the entry you most want gone -- could not be removed.z -x /deleted/dir(andz -xR) now canonicalizes the argument without requiring it to exist, resolving symlinks in as much of the path as is still present. The same change fixes a crash on Zsh 4.3.11, where an upstream bug makes${x:A}segfault when the top-level component of the path is missing:z -x /gone/sub-- or aZSHZ_DATApointing into a missing top-level directory -- could kill the shell there.- More robust startup and operation. A version check on an unsupported Zsh no longer risks exiting your interactive shell.
Smaller changes you may notice
None of these should need any action from you, but they are the things a v1 user is most likely to spot:
-rand-tcan no longer be combined. They name mutually exclusive sort orders -- rank versus recency -- soz -rt foowas always contradictory, and-tsilently took precedence. It is now rejected with an error message instead of silently picking one.- A lockfile appears next to your database. Writes are serialized through
~/.z.lock(or$ZSHZ_DATAplus.lock). It is created600, it stays empty, and it is deliberately never deleted -- removing a lockfile that another shell has already opened would reintroduce the very race it exists to prevent. Wherezsh/systemis missing andzsystem flockis therefore unavailable -- MobaXterm's cut-down Cygwin is the case in practice -- you will see a short-lived~/.z.lock.ddirectory instead, which is created and removed for each write. SeeZSHZ_LOCK_TIMEOUTfor how that fallback behaves. - An existing database gets tightened to
600on the next write. If your~/.zpredates this release and is644, the first write will re-mode it. This is the #92 fix applied to databases you already have, not just newly created ones. Setting the mode is treated as part of the write rather than a courtesy afterwards: if it cannot be done, the write is abandoned and reports failure instead of publishing a database that anyone on the machine could read.
The dated entries below remain the historical record of changes leading up to v2.0.
Here are the older features and updates.
- May 6, 2026
- Zsh-z will now handle paths with dollar signs (
$) in them. - Workaround for Zsh emulating
sh/bash/ksh.
- Zsh-z will now handle paths with dollar signs (
- May 1, 2026
- Various tab completion bugs resolved.
- April 27, 2026
- Fixes a bug where re-sourcing the script caused an infinite loop when tab was pressed. Props to @maheshpec for successfully diagnosing the problem.
- Fixes a bug where the completion widget was not identifying options correctly.
- March 31, 2026
- When the user hits tab after entering a command-line argument that uses spaces as wildcards (e.g.,
z us lo bi), the command line is clear of detritus (i.e., it looks likez /usr/local/bininstead ofz us lo /usr/local/bin). - Improved test for Docker containers.
- When the user hits tab after entering a command-line argument that uses spaces as wildcards (e.g.,
- August 24, 2023
- Zsh-z will now run when
setopt NO_UNSEThas been enabled (props @ntninja).
- Zsh-z will now run when
- August 23, 2023
- Better logic for loading
zsh/files(props @z0rc).
- Better logic for loading
- August 2, 2023
- Zsh-z still uses the
zsh/filesmodule when possible but will fall back on the standardchown,mv, andrmcommands in its absence.
- Zsh-z still uses the
- April 27, 2023
- Zsh-z now allows the user to specify the directory-changing command using the
ZSHZ_CDenvironment variable (default:builtin cd; props @basnijholt).
- Zsh-z now allows the user to specify the directory-changing command using the
- January 27, 2023
- If the database file directory specified by
ZSHZ_DATAor_Z_DATAdoes not already exist, create it (props @mattmc3).
- If the database file directory specified by
- June 29, 2022
- Zsh-z is less likely to leave temporary files sitting around (props @mafredri).
- June 27, 2022
- A bug was fixed which was preventing paths with spaces in them from being updated (#61).
- If writing to the temporary database file fails, the database will not be clobbered (props @mafredri).
- December 19, 2021
- Zsh-z will now display tildes for
HOMEduring completion whenZSHZ_TILDE=1has been set.
- Zsh-z will now display tildes for
- November 11, 2021
- A bug was fixed which was preventing ranks from being incremented.
--addhas been made to work with relative paths and has been documented for the user.
- October 14, 2021
- Completions were being sorted alphabetically, rather than by rank; this error has been fixed.
- September 25, 2021
- Orthographical change: "Zsh," not "ZSH."
- September 23, 2021
z -xRwill now remove a directory and its subdirectories from the database.z -xandz -xRcan now take an argument; without one,PWDis assumed.
- September 7, 2021
- Fixed the unload function so that it removes the
$ZSHZ_CMDalias (default:z).
- Fixed the unload function so that it removes the
- August 27, 2021
- Using
print -v ... -finstead ofprint -vto work around longstanding bug in Zsh involvingprint -vand multibyte strings.
- Using
- August 13, 2021
- Fixed the explanation string printed during completion so that it may be formatted with
zstyle. - Zsh-z now declares
ZSHZ_EXCLUDE_DIRSas an array with unique elements so that you do not have to.
- Fixed the explanation string printed during completion so that it may be formatted with
- July 29, 2021
- Temporarily disabling the use of
print -v, which was mangling CJK multibyte strings.
- Temporarily disabling the use of
- July 27, 2021
- Internal escaping of path names now works with older versions of Zsh.
- Zsh-z now detects and discards any incomplete or incorrectly formatted database entries.
- July 10, 2021
- Setting
ZSHZ_TRAILING_SLASH=1makes it so that a search pattern ending in/can match the end of a path; e.g.z foo/can match/path/to/foo.
- Setting
- June 25, 2021
- Setting
ZSHZ_TILDE=1displays theHOMEdirectory as~.
- Setting
- May 7, 2021
- Setting
ZSHZ_ECHO=1will cause Zsh-z to display the new path when you change directories. - Better escaping of path names to deal with paths containing the characters
\`()[].
- Setting
- February 15, 2021
- Ranks are displayed the way
rupa/znow displays them, i.e. as large integers. This should help Zsh-z to integrate with other tools.
- Ranks are displayed the way
- January 31, 2021
- Zsh-z is now efficient enough that, on MSYS2 and Cygwin, it is faster to run it in the foreground than it is to fork a subshell for it. (Behavior superseded in v2.0.)
_zshz_precmdsimply returns ifPWDisHOMEor inZSHZ_EXCLUDE_DIRS, rather than waiting forzshzto do that.
- January 17, 2021
- Made sure that the
PUSHD_IGNORE_DUPSoption is respected.
- Made sure that the
- January 14, 2021
- The
z -hhelp text now breaks at spaces. z -lwas not working for Zsh version < 5.
- The
- January 11, 2021
- Major refactoring of the code.
z -lrandz -ltwork as expected.EXTENDED_GLOBhas been disabled within the plugin to accommodate old-fashioned Windows directories with names such asProgra~1.- Removed
zshelldocdocumentation.
- January 6, 2021
- I have corrected the frecency routine so that it matches
rupa/z's math, but for the present, Zsh-z will continue to display ranks as 1/10000th of what they are inrupa/z-- they had to multiply theirs by 10000 to work aroundbash's inadequacies at dealing with decimal fractions.
- I have corrected the frecency routine so that it matches
- January 5, 2021
- If you try
z foo, andfoois not in the database but${PWD}/foois a valid directory, Zsh-z willcdto it.
- If you try
- December 22, 2020
ZSHZ_CASE: when set toignore, pattern matching is case-insensitive; when set tosmart, patterns are matched case-insensitively when they are all lowercase and case-sensitively when they have uppercase characters in them (a behavior very much like Vim'ssmartcasesetting).ZSHZ_KEEP_DIRSis an array of directory names that should not be removed from the database, even if they are not currently available (useful when a drive is not always mounted).- Symlinked database files were having their symlinks overwritten; this bug has been fixed.
Installation
General observations
This plugin can be installed simply by putting the various files in a directory together and by sourcing zsh-z.plugin.zsh in your .zshrc:
source /path/to/zsh-z.plugin.zsh
Tab completion requires compinit. _zshz must also be in the same directory as zsh-z.plugin.zsh. The frameworks handle both of these requirements, but if you are not using a framework, put
autoload -U compinit; compinit
in your .zshrc somewhere below where you source zsh-z.plugin.zsh -- the plugin adds its directory to fpath at source time, and compinit needs to see it there in order to find _zshz.
If you add
zstyle ':completion:*' menu select
to your .zshrc, your completion menus will look very nice. This zstyle invocation should work with any of the frameworks below as well.
For antigen users
Add the line
antigen bundle agkozak/zsh-z
to your .zshrc, somewhere above the line that says antigen apply.
For Oh My Zsh users
Zsh-z is now included as part of Oh My Zsh! As long as you are using an up-to-date installation of Oh My Zsh, you can activate Zsh-z simply by adding z to your plugins array in your .zshrc, e.g.,
It is as simple as that.
If, however, you prefer always to use the latest version of Zsh-z from the agkozak/zsh-z repo, you may install it thus:
git clone https://github.com/agkozak/zsh-z ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-z
and activate it by adding zsh-z to the line of your .zshrc that specifies plugins=(), e.g., plugins=( git zsh-z ).
For prezto users
Execute the following command:
git clone https://github.com/agkozak/zsh-z.git ~/.zprezto-contrib/zsh-z
Then edit your ~/.zpreztorc file. Make sure the line that says
zstyle ':prezto:load' pmodule-dirs $HOME/.zprezto-contrib
is uncommented. Then find the section that specifies which modules are to be loaded; it should look something like this:
zstyle ':prezto:load' pmodule \
'environment' \
'terminal' \
'editor' \
'history' \
'directory' \
'spectrum' \
'utility' \
'completion' \
'prompt'
Add a backslash to the end of the last line and add 'zsh-z' to the list, e.g.,
zstyle ':prezto:load' pmodule \
'environment' \
'terminal' \
'editor' \
'history' \
'directory' \
'spectrum' \
'utility' \
'completion' \
'prompt' \
'zsh-z'
Then relaunch zsh.
For zcomet users
Simply add
zcomet load agkozak/zsh-z
to your .zshrc (below where you source zcomet.zsh and above where you run zcomet compinit).
For zgen users
Add the line
somewhere above the line that says zgen save. Then run
to refresh your init script.
For Zim
Add the following line to your .zimrc:
zmodule https://github.com/agkozak/zsh-z
Then run
and restart your shell.
For Zinit users
Add the line
to your .zshrc.
Zsh-z supports zinit's unload feature; just run zinit unload agkozak/zsh-z to restore the shell to its state before Zsh-z was loaded. It gives back only what it took: the fpath entry is removed only if Zsh-z was the one that added it, so a directory your plugin manager put there is left alone, and the Tab-completion mapping is dropped only while it still points at Zsh-z's own completer.
For Znap users
Add the line
znap source agkozak/zsh-z
somewhere below the line where you source Znap itself.
For zplug users
Add the line
somewhere above the line that says zplug load. Then run
to install Zsh-z.
Command Line Options
--addAdd a directory to the database-cOnly match subdirectories of the current directory-eEcho the best match without going to it-hDisplay help-lList all matches without going to them-rMatch by rank (i.e. how much time you spend in directories)-tTime -- match by how recently you have been to directories-xRemove a directory (by default, the current directory) from the database-xRRemove a directory (by default, the current directory) and its subdirectories from the database
Settings
Zsh-z has environment variables (they all begin with ZSHZ_) that change its behavior if you set them. You can also keep your old ones if you have been using rupa/z (whose environment variables begin with _Z_).
ZSHZ_CASEcan beignore, for case-insensitive matching, orsmart, for Vim-likesmartcasematching; see below (default: empty, i.e., a case-sensitive match is tried first, then a case-insensitive one)ZSHZ_CMDchanges the command name (default:z)ZSHZ_CDspecifies the default directory-changing command (default:builtin cd)ZSHZ_COMPLETIONcan be'frecent'(default) or'legacy', depending on whether you want your completion results sorted according to frecency or simply sorted alphabeticallyZSHZ_DATAchanges the database file (default:~/.z)ZSHZ_ECHOdisplays the new path name when changing directories (default:0)ZSHZ_EXCLUDE_DIRSis an array of directories to keep out of the database (default: empty)ZSHZ_KEEP_DIRSis an array of directories that should not be removed from the database, even if they are not currently available (useful when a drive is not always mounted) (default: empty)ZSHZ_LOCK_TIMEOUTis the number of seconds to wait for the database lock before giving up on a write (default:1). Wherezsh/systemis unavailable andzsystem flockcannot be used -- MobaXterm's cut-down Cygwin, for instance -- Zsh-z serializes writes with an atomicmkdiron~/.z.lock.dinstead, and this setting bounds the wait the same way. That fallback has no equivalent of the kernel releasing a lock when its holder dies, so a lock directory older than 30 seconds is treated as abandoned and cleared; a write takes milliseconds, so nothing legitimate is ever that old. A write that times out is dropped silently -- the automaticprecmdadd is best-effort -- so if the database seems to stop updating, the lock is probably contended: runz --add .by hand and check$?. A2is a lock-acquisition timeout, which confirms contention -- look for a stale process holding~/.z.lock(or a leftover~/.z.lock.ddirectory on the fallback path), or raise this setting. A1is something else entirely, usually a permissions or ownership problem, such as a root-owned~/.zor~/.z.lockleft behind by an earliersudo -ssession.ZSHZ_MAX_SCOREis the maximum combined score the database entries can have before they begin to age and potentially drop out of the database (default: 9000)ZSHZ_NO_RESOLVE_SYMLINKSprevents symlink resolution (default:0)ZSHZ_OWNERis the username the database belongs to; set it to your own login name to keepzworking in a root shell, such as undersudo -E -s; see below (default: empty)ZSHZ_TILDEdisplays the name of theHOMEdirectory as a~(default:0)ZSHZ_TRAILING_SLASHmakes it so that a search pattern ending in/can match the final element in a path; e.g.,z foo/can match/path/to/foo(default:0)ZSHZ_UNCOMMONchanges the logic used to calculate the directory jumped to; see below (default:0)
Case sensitivity
The default behavior of Zsh-z is to try to find a case-sensitive match. If there is none, then Zsh-z tries to find a case-insensitive match.
Some users prefer simple case-insensitivity; this behavior can be enabled by setting
If you like Vim's smartcase setting, where lowercase patterns are case-insensitive while patterns with any uppercase characters are treated case-sensitively, try setting
ZSHZ_UNCOMMON
A common complaint about the default behavior of rupa/z and Zsh-z involves "common prefixes." If you type z code and the best matches, in increasing order, are
/home/me/code/foo
/home/me/code/bar
/home/me/code/bat
Zsh-z will see that all possible matches share a common prefix and will send you to that directory -- /home/me/code -- which is often a desirable result. But if the possible matches are
/home/me/.vscode/foo
/home/me/code/foo
/home/me/code/bar
/home/me/code/bat
then there is no common prefix. In this case, z code will simply send you to the highest-ranking match, /home/me/code/bat.
You may enable an alternate, experimental behavior by setting ZSHZ_UNCOMMON=1. If you do that, Zsh-z will not jump to a common prefix, even if one exists. Instead, it chooses the highest-ranking match -- but it drops any subdirectories that do not include the search term. So if you type z bat and /home/me/code/bat is the best match, that is exactly where you will end up. If, however, you had typed z code and the best match was also /home/me/code/bat, you would have ended up in /home/me/code (because code was what you had searched for). This feature is still in development, and feedback is welcome.
ZSHZ_OWNER
If you are using root privileges while keeping your personal home directory as HOME (as is the case with sudo -E -s), conflicts with file ownership arise. You can resolve them by using ZSHZ_OWNER. If you set this variable to your username, Zsh-z will be able to use your personal datafile, restoring its proper ownership with every write.
Setting ZSHZ_OWNER makes one thing stricter, because a privileged shell is then writing to a path that an unprivileged user controls: Zsh-z follows a symlink on the way to the datafile -- the file itself, or any parent directory -- only if root owns the link, and reports an error instead of writing otherwise. Symlinked system directories, such as /home → /usr/home on the BSDs or /var → /private/var on macOS, belong to root and still resolve. With ZSHZ_OWNER unset none of this applies: a symlinked ~/.z is followed exactly as before, so pointing it at synced storage keeps working.
Making --add Work for You
Zsh-z internally uses the --add option to add paths to its database. @zachriggle pointed out to me that users might want to use --add themselves, so I have altered it a little to make it more user-friendly.
A good example might involve a directory tree that has Git repositories within it. The working directories could be added to the Zsh-z database as a batch with
for i in $(find $PWD -maxdepth 3 -name .git -type d); do
z --add ${i:h}
done
(As a Zsh user, I tend to use ** instead of find, but it is good to see how deep your directory trees go before doing that.)
Performance
One of the goals of the rewrite that culminated in v2.0 was to make Zsh-z simultaneously more stable and faster. On modern Zsh, Zsh-z outpaces rupa/z's z.sh at adding, searching, and listing. Representative figures (N = 200 database entries, medians of seven interleaved runs on a Core i7-12700 desktop under WSL2):
Modern Zsh (5.9) -- Zsh-z vs. rupa/z:
| Operation | rupa/z (z.sh) |
Zsh-z | Winner |
|---|---|---|---|
add |
6.25 ms/op | 1.99 ms/op | Zsh-z ~3.14x |
search |
6.51 ms/op | 3.62 ms/op | Zsh-z ~1.80x |
list |
8.93 ms/op | 4.36 ms/op | Zsh-z ~2.05x |
Zsh 4.3.11 (the oldest supported release) -- Zsh-z vs. rupa/z:
| Operation | rupa/z (z.sh) |
Zsh-z | Winner |
|---|---|---|---|
add |
5.00 ms/op | 2.92 ms/op | Zsh-z ~1.71x |
search |
4.93 ms/op | 4.47 ms/op | Zsh-z ~1.10x |
list |
7.92 ms/op | 6.08 ms/op | Zsh-z ~1.30x |
Removal is absent from these tables: in rupa/z, the per-prompt hook re-adds the current directory right after z -x removes it, so the two implementations' -x are not doing comparable work.
Relative to the previous generation of Zsh-z, the v2.0 read path is dramatically faster -- on modern Zsh, listing the whole database is about 2.4x faster and searching about 1.6x faster.
Other Improvements to the Original Functionality of rupa/z
z -xworks: a directory you remove stays removed.- Zsh-z is compatible with Solaris.
- Zsh-z uses the "new"
zshcompsyscompletion system instead of the oldcompctlone. - No error message is displayed when the database file has not yet been created.
- Special characters (e.g.,
[) in directory names are now supported. - If
z -lreturns only one match, a common root is not printed. - Exit status codes are more logical.
- Completions now work with options
-c,-r, and-t. - If
~/fooand~/foobare matches,~/foois no longer considered the common root. Only a common parent directory can be a common root. z -xand the new, recursivez -xRcan now accept an argument so that you can remove directories other thanPWDfrom the database.- Zsh-z inherits
rupa/z's behavior of allowing spaces as wildcards (e.g.,z us lo bimight take you to/usr/local/bin), but now completion of such command lines does not result in visual detritus.
Migrating from Other Tools
Zsh-z's database format is identical to that of rupa/z. You may switch freely between the two tools (I still use rupa/z for bash). fasd also uses that database format, but it stores it by default in ~/.fasd, so you will have to cp ~/.fasd ~/.z if you want to use your old directory history.
If you are coming to Zsh-z (or even to the original rupa/z, for that matter) from autojump, try using my jumpstart-z tool to convert your old database to the Zsh-z format, or simply run
awk -F "\t" '{printf("%s|%0.f|%s\n", $2, $1, '"$(date +%s)"')}' < /path/to/autojump.txt > ~/.z
COMPLETE_ALIASES
z, or any alternative you set up using $ZSHZ_CMD or $_Z_CMD, is an alias. setopt COMPLETE_ALIASES divorces the tab completion for aliases from the underlying commands they invoke, which historically broke Zsh-z's tab completion. Zsh-z now handles this automatically: the first Tab press registers the alias name with _zshz so completion works under COMPLETE_ALIASES without any extra setup.
That registration happens inside Zsh-z's own Tab widget. If a plugin loaded after Zsh-z replaces the Tab binding without invoking the previous widget, Zsh-z's widget never runs and the registration does not happen. Under COMPLETE_ALIASES, you would then have no completion for z. Once compinit has run, adding
compdef _zshz ${ZSHZ_CMD:-${_Z_CMD:-z}}
below setopt COMPLETE_ALIASES in your .zshrc fixes that, and it is harmless if the automatic registration has already run.
