Getting the right virtual environement

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.


Previous: One of the best parts of being a dad. Next: Why dunder?