If you use zoxide and notice the z alias is missing in non-interactive bash shells — cron jobs, CI scripts, or subshells spawned by tools — this is why and how to fix it.
Why z doesn't work in non-interactive bash shells
When you run eval "$(zoxide init bash)" in your ~/.zshrc or ~/.bashrc, it defines the z shell function for interactive sessions. But non-interactive bash shells (launched with bash -c ... or by tools internally) don't source those files. They skip .bashrc entirely.
So z is simply never defined, and you get command not found.
The fix: BASH_ENV
Bash has one hook for non-interactive shells: the BASH_ENV environment variable. Before running any commands, bash reads and sources the file pointed to by $BASH_ENV — no flags needed, no special config.
Create a minimal env file:
echo 'export _ZO_DOCTOR=0' > ~/.bash_env
echo 'eval "$(zoxide init bash)"' >> ~/.bash_env
_ZO_DOCTOR=0 suppresses the warning zoxide prints when it detects a non-interactive shell. It must come before the eval.
Then export BASH_ENV from your shell so it's inherited by every subprocess:
echo 'export BASH_ENV="$HOME/.bash_env"' >> ~/.zshrc
source ~/.zshrc
That's it. Any bash subshell spawned from your terminal session will now have z available.
Verify it
cd /tmp && z your-project && pwd
No warnings, navigates correctly.
Why not just add it to ~/.bashrc?
Non-interactive bash shells don't source ~/.bashrc. That file is only read by interactive non-login bash shells. BASH_ENV is the only official hook bash provides for non-interactive shells.
Keep ~/.bash_env minimal — only what you actually need in non-interactive contexts. Don't dump your full shell config there; it runs on every bash invocation.