Published April 29, 2026
I work in a lot of repos, professionally and in my off time. One of the biggest things I've dealt with is being in the wrong virtual environment for the codebase I happen to mucking around it. Working on my site I need to be in the right virtual enviornment for that, and working in one of the many repos I deal with at work requires it too. As someone who lives in a terminal, this can be (as you might imagine) somewhat frustrating.
So I decided to figure out what I could do to help make sure that I'm always in the right venv for that directory. I looked at
direnv but decided that was overkill for what I needed - I just needed the right venv to be active. So I did some
searching to see if I could do something so that when I did cd <directory> it would load the correct venv. Turns out, it's not too
hard. zsh lets you add hooks, so I did:
add-zsh-hook chpwd _check_venv
Now I just needed to write the _check_venv function that would do the work:
function _check_venv()
{
if git rev-parse &> /dev/null; then
[[ $VIRTUAL_ENV =~ "$(basename $(git rev-parse --show-toplevel))" ]] && return
fi
local venv_path="$(check_path "$PWD" ".venv")"
if [[ -n "$venv_path" ]]; then
_activate "$venv_path"
else
_deactivate
fi
}
So let's break that down. git rev-parse just lets me know if I'm in a repo. It will exit with an error code if you're not. If I am in
a repo, I try to see if I'm in the venv for the repo - if I am, there's nothing to do. The next bit, check_path "$PWD" ".venv" does
a recursive check up the directory tree until it hits / or a directory with .venv in it:
function check_path()
{
local check_dir="$1"
local check_file="$2"
local stop_point="${3-$HOME}"
if [[ -d "${check_dir}/$check_file" ]]; then
printf "${check_dir}/$check_file"
return
else
# Abort search at file system root or HOME directory (latter is a performance optimisation).
if [[ "$check_dir" = "/" || "$check_dir" = "$stop_point" ]]; then
return
fi
check_path "$(dirname "$check_dir")" "$check_file"
fi
}
If it finds somthing, it activates the virtual environment, if not and a venv is active, it deactivates it.
function _activate() {
local venv_dir="$1"
# Don't reactivate an already activated virtual environment
if [[ -z "$VIRTUAL_ENV" || "$venv_dir" != "$VIRTUAL_ENV" ]]; then
source "$venv_dir/bin/activate"
fi
}
function _deactivate() {
if [[ -n "$VIRTUAL_ENV" ]]; then
deactivate
fi
}
All in all I have found this to be a huge help in saving time and frustration, generally making my life a little bit easier. I will say that I did have a bit of a concern that this might make changing directories take longer, but if it has it has been negligible.
*I did this in zsh because that's the shell I use, but the functions should work in bash as well, I'm just not sure how to
hook it in to trigger with each directory change.