Automatic UV-based Python virtual environment management for your shell.
auto-uv-env watches directory changes, discovers the nearest pyproject.toml, creates a project-local virtual environment with uv, and activates/deactivates it automatically.
+-------------------------+ --check-safe +-------------------------+
| shell hook |------------------------>| auto-uv-env |
| bash/zsh/fish adapter |<------------------------| directive producer |
+------------+------------+ KEY=VALUE lines +------------+------------+
| |
| applies directives | reads pyproject.toml
v | validates venv name
+------+--------------------------+ |
| source/deactivate virtualenv | |
| uv python install / uv venv |<-----------------------+
+---------------------------------+
curl -LsSf https://astral.sh/uv/install.sh | shInstaller script:
curl -LsSf https://auto-uv-env.ashwch.com/install.sh | shHomebrew:
brew tap ashwch/tap
brew install auto-uv-envZsh (~/.zshrc):
source $(brew --prefix)/share/auto-uv-env/auto-uv-env.zsh
# or: source /usr/local/share/auto-uv-env/auto-uv-env.zshBash (~/.bashrc):
source $(brew --prefix)/share/auto-uv-env/auto-uv-env.bash
# or: source /usr/local/share/auto-uv-env/auto-uv-env.bashFish (~/.config/fish/config.fish):
source (brew --prefix)/share/auto-uv-env/auto-uv-env.fish
# or: source /usr/local/share/auto-uv-env/auto-uv-env.fishcd my-project/src/app
# π Setting up Python 3.11 with UV...
# β
Virtual environment created
# π UV environment activated (Python 3.11.x)
cd ..
# β¬οΈ Deactivated UV environment- Walk upward from
$PWDto find the nearestpyproject.toml. - If
.auto-uv-env-ignoreappears first, skip activation for that subtree. - Read
requires-pythonfrom the discovered project root. - Create
<project-root>/.venvwhen missing. - Activate on entry and deactivate only environments managed by auto-uv-env.
- If a manual venv is active, auto-uv-env does not override it.
Environment variables:
AUTO_UV_ENV_QUIET=1: suppress status messages.AUTO_UV_ENV_VENV_NAME=.venv: change the venv directory name.AUTO_UV_ENV_PYTHON_VERSION: exported Python version of the active managed env.
pyproject.toml example:
[project]
name = "my-project"
requires-python = ">=3.11"Disable in a subtree:
touch .auto-uv-env-ignoreIgnore precedence:
- Upward discovery stops activation when ignore is found before
pyproject.toml. - Entering an ignored subtree deactivates the currently managed environment.
auto-uv-env --help
auto-uv-env --version
auto-uv-env --check-safe [DIR]
auto-uv-env --diagnose [DIR]
auto-uv-env --validate--check-safe emits directive lines (for shell adapters), for example:
CREATE_VENV=1
PYTHON_VERSION=3.11
MSG_SETUP=π Setting up Python 3.11 with UV...
ACTIVATE=/path/to/project/.venv
The shell adapters (bash, zsh, fish) run inside your live shell session, so they have to be boring and predictable.
Rule 1: never let one activation run recursively trigger another
Rule 2: always create the venv at an explicit project-root path
Why:
Without Rule 1
--------------
hook starts
-> creates venv
-> activation changes shell state
-> shell/plugin fires hook again
-> setup repeats
-> "Setting up Python ..." forever
Without Rule 2
--------------
hook starts
-> cd into project root
-> run uv venv
-> shell state now depends on directory-changing side effects
With both rules:
hook starts
-> compute project root
-> create /absolute/project/.venv directly
-> activate once
-> finish
This is why the adapters now:
- use a small re-entry guard while activation is already in progress
- call
uv venv /absolute/path/to/.venvinstead of relying oncd - set
VIRTUAL_ENV_DISABLE_PROMPT=1only while sourcing the activation script, then restore the prior shell state - keep the main executable declarative and the shell adapter imperative
auto-uv-env owns environment activation
your shell theme owns prompt rendering
Why this matters:
Bad
---
source bin/activate
-> activation script rewrites PS1 / prompt function
-> starship or custom shell prompt loses styling
Good
----
temporarily set VIRTUAL_ENV_DISABLE_PROMPT=1 while sourcing bin/activate
then restore the previous shell variable state
-> environment variables are imported
-> prompt styling stays under the user's control
- v1.0.7 delivered roughly 93% startup overhead improvement versus earlier behavior.
- Typical startup overhead is low in Python projects and effectively near-zero in non-project directories.
- Directory-change overhead is typically sub-millisecond.
auto-uv-env is a strong fit when you want:
- UV-first Python environment automation (no extra orchestration layer)
pyproject.toml-driven project discovery- Automatic activate/deactivate across Bash, Zsh, and Fish
- Project-local
.venvbehavior with minimal setup
If you are deciding between this and direnv, mise, pyenv-virtualenv, or shell-specific plugins, see the decision guide:
docs/alternatives.md.
The install script has its own first-principles rule:
extract first -> install second -> clean up last
Why:
installer shell
-> ask helper to download + extract release
-> helper returns extracted path
-> installer copies files into final install dirs
-> installer removes temporary files
If cleanup happens too early, installation can fail with a "No such file or directory" error while copying from the extracted release tree.
The repository now includes a dedicated installer regression test for this lifecycle:
./test/test-installer.shThat test runs the real installer flow with mocked downloads, so future changes can prove the temporary extracted directory stays alive until installation completes.
- Installation details:
docs/installation.md - Usage and troubleshooting:
docs/usage.md - Contributing guide:
docs/contributing.md - Alternatives decision guide:
docs/alternatives.md - Contributor quick entrypoint:
CONTRIBUTE.md - Release runbook:
RELEASE.md
Essential checks:
./test/test.sh
./test/test-security.sh
./test/test-shell-integrations.sh
./test/test-deleted-venv.sh
uv tool run pre-commit run --all-filesIf your shell currently has an active venv, use a sanitized test invocation:
env -u VIRTUAL_ENV -u _AUTO_UV_ENV_ACTIVATION_DIR -u AUTO_UV_ENV_PYTHON_VERSION ./test/test.shRelease reminder: merging a PR does not create a GitHub release. Releases are published on v* tag pushes; follow RELEASE.md.
Docs publishing reminder: https://auto-uv-env.ashwch.com/ is deployed by docs.yml on pushes to main.
AGENTS.md: canonical architecture, workflows, quality gates, and safety rules.CLAUDE.md: minimal Claude entrypoint that sourcesAGENTS.md.
MIT License. See LICENSE.
Created by Ashwini Chaudhary.