asdf Global vs Local Python Versions: When .tool-versions Breaks Your Virtual Environment
Snakes returning to their hibernacula rely on consistent thermal gradients and known paths to survive the winter. A sudden shift in the external environment can disrupt these cues, making the familiar path treacherous. Similarly, when using asdf for Python version management, a virtual environment (the project’s “den”) relies on a consistent path to the Python interpreter (the “cue”). When .tool-versions switches the global Python version, the shim path changes, breaking the link to the previously created venv.
In this guide, we explain the root cause and walk through several approaches to resolve it—tested on Ubuntu 24.04 and macOS Sonoma.
How asdf Resolves Python Versions: Global and Local Precedence
asdf prioritizes version resolution hierarchically:
- Project-local
./.tool-versions(highest priority) - Parent directories up to
$HOME/.tool-versions(global)
# Global default
$ asdf global python 3.12.3
# Project-specific (creates .tool-versions)
$ cd myproject
$ asdf local python 3.11.9
$ cat .tool-versions\npython 3.11.9\n```
Verify with these commands:\n```bash\n$ asdf current\npython 3.11.9 /home/user/project/.tool-versions\n\n$ which python\n/home/user/.asdf/shims/python\n\n$ python --version\nPython 3.11.9\n```\n\nOf course, paths and exact versions will vary based on your setup.
Shims (`~/.asdf/shims/python`) dynamically point to the resolved version's binary. Venvs capture this shim path at creation time.
## Why .tool-versions Breaks Virtual Environments
Standard `python -m venv .venv` records `sys.executable` (shim path) in `pyvenv.cfg` and shebangs.
**Scenario:**
1. Global Python 3.12: `python -m venv project1/.venv` → venv points to 3.12 shim.
2. `cd project2` (`.tool-versions: python 3.11.9`) → shim now points to 3.11.9.
3. Activate `project1/.venv` → venv loads wrong interpreter, libraries mismatch.
Think of it like a snake returning to its den using a specific scent trail. If the wind direction (global version) changes, the scent trail gets disrupted, and the snake can't find its way back to the correct den (venv). The virtual environment was created with a specific Python version in mind, but when the shim path changes, it's like the entrance to the den has moved.
Error examples:
RuntimeError: failed to init libpython3.12.so
or
python —version # Inside venv: Python 3.11.9 (but packages built for 3.12)
## Diagnosing the Problem\n\nWhen you suspect a shim path mismatch, we diagnose it step by step:\n\n1. Check the effective version:\n ```bash\n $ asdf current python\n python 3.11.9 /home/user/project/.tool-versions\n ```
2. Inspect the venv:\n ```bash\n $ . .venv/bin/activate\n (.venv) $ which python\n /abs/path/project/.venv/bin/python\n \n (.venv) $ python -c "import sys; print(sys.executable)"\n /abs/path/project/.venv/bin/python\n \n (.venv) $ cat .venv/pyvenv.cfg\n home = /abs/path/project/.venv\n include-system-site-packages = false\n version = 3.12.3\n executable = /home/user/.asdf/shims/python\n prompt path = \n ```\n \n Notice the `executable` still points to the shim created under the original version.
3. Trace the shim to its real binary:\n ```bash\n $ ls -la $(which python)\n lrwxrwxrwx 1 user group 64 date /home/user/.asdf/shims/python -> ../../../installs/python/3.11.9/bin/python\n ```\n \n The symlink target reveals the actual binary asdf resolved at shim invocation time.
## Approaches to Fixing and Preventing Virtual Environment Breaks\n\nWe present four approaches below. Each suits different workflows; consider your project's size, team setup, and long-term maintenance needs.\n\n### 1. Recreate the Virtual Environment After Setting the Local Version
Always create/ recreate post-`asdf local`:
```bash
$ cd project
$ asdf local python 3.11.9 # Or ensure .tool-versions exists
$ rm -rf .venv
$ python -m venv .venv
$ . .venv/bin/activate
$ pip install -r requirements.txt
Note: Add .venv/ to your .gitignore file to avoid committing virtual environments.\n\nThis approach guarantees the venv uses the correct interpreter, though recreating it means reinstalling all dependencies after version changes—a process that can take time for large projects. However, it’s reliable and requires no additional tools.\n\n### 2. Use the pyenv-virtualenv Plugin with asdf
asdf-python leverages pyenv; add asdf-pyenv-virtualenv:
$ asdf plugin add pyenv-virtualenv https://github.com/asdf-community/asdf-pyenv-virtualenv.git
$ asdf install pyenv-virtualenv latest
$ asdf global pyenv-virtualenv latest
Create:
$ pyenv virtualenv 3.11.9 myproject-env
$ pyenv local myproject-env # Creates .python-version
Compatible with .tool-versions via legacy support.\n\npyenv-virtualenv integrates well with asdf’s pyenv base, providing venv management without manual recreation. The downside: it introduces another plugin and creates .python-version files alongside .tool-versions, potentially complicating multi-tool setups.\n\n### 3. Integrate with direnv for Auto-Switching
Install direnv + asdf-direnv plugin for automatic version + venv activation:
$ asdf plugin add direnv
$ echo "use python" > .envrc # Auto asdf + venv
$ direnv allow
Sets version on cd, automatically handling venvs if configured.\n\nWhile powerful for automation, direnv adds shell integration overhead and requires learning .envrc syntax. It’s ideal for teams enforcing consistent environments but may feel heavy for solo projects.\n\n### 4. Migrate to mise (asdf Successor)
For native venv handling, switch to mise (Rust, faster):
$ asdf plugin add mise
$ mise install python@3.12.3
# Preserves .tool-versions\n```\n\nmise offers better native venv support and faster performance due to its Rust implementation. However, migrating from asdf involves learning new commands and potentially updating CI/CD pipelines— a worthwhile long-term investment for speed gains, though not without upfront cost.\nSee [article 1 in series](2-asdf-python-plugin-installing-multiple-python-versions-on-ubuntu-24-04-lts.md).\n\n## Best Practices for asdf + Venvs
- **Pin versions:** Use `pyproject.toml` or `requirements.txt` with `--only-binary=:all:`.
- **Reshim post-pip:** `asdf reshim python` after `pip install` binaries (e.g., black).
- **CI/CD:** Set `asdf install` in workflow matching `.tool-versions`.
- **Multi-version fallback:** `asdf global python 3.12.3 3.11.9` for graceful degradation.
## Troubleshooting Edge Cases
| Issue | Fix |
|-------|-----|
| `libpython not found` | Recreate venv; ensure deps (Ubuntu: `libssl-dev libffi-dev`) |
| Shim not updating | `asdf reshim python`; `hash -r` (clear cache) |
| macOS ARM | Rosetta not required—asdf-python supports native binaries. |
| Docker | Copy `.tool-versions`, run `asdf install` in container. |
## Conclusion
asdf's `.tool-versions` enables reproducible environments, but we need to recreate venvs after switching versions—whether global or local. By choosing the approach that fits your workflow—or combining them—you can achieve consistent Python development environments.
**Key Takeaways:**
- Local `.tool-versions` overrides global; shims adapt dynamically.
- Always run `python -m venv` after setting a version.
- Use pyenv-virtualenv or direnv for automation.
- Related: [asdf on Ubuntu 24.04](2-asdf-python-plugin-installing-multiple-python-versions-on-ubuntu-24-04-lts.md), [mise benchmarks](3-mise-vs-asdf-for-python-development-performance-benchmarks-on-m1-m2-macs.md).
If you encounter issues, check the [asdf-python GitHub](https://github.com/asdf-community/asdf-python) for additional support.
<script>
const relatedLinks = [
{
title: "asdf Python Plugin: Installing Multiple Python Versions on Ubuntu 24.04 LTS",
url: "/articles/asdf-python-plugin-installing-multiple-python-versions-on-ubuntu-24-04-lts",
description: "Learn how to install and configure asdf-vm's Python plugin on Ubuntu 24.04 LTS. This foundational guide covers prerequisites, installation steps, and initial configuration for seamless Python version management."
},
{
title: "mise vs asdf for Python Development: Performance Benchmarks on M1/M2 Macs",
url: "/articles/mise-vs-asdf-for-python-development-performance-benchmarks-on-m1-m2-macs",
description: "Compare the performance of mise and asdf on Apple Silicon. We measure installation speed, shim overhead, and version switching to help you decide which modern version manager best fits your workflow."
},
{
title: "asdf reshim Errors After pip install: Complete Troubleshooting Guide",
url: "/articles/asdf-reshim-errors-after-pip-install-complete-troubleshooting-guide",
description: "When pip installs new executables, asdf requires reshimming to recognize them. This guide covers the top 10 reshim errors—including permission denied, missing shims, and PATH issues—with step-by-step fixes."
},
{
title: "Python Version Management: Compare asdf, mise, and pyenv",
url: "/articles/python-version-management-asdf-mise-pyenv",
description: "A comprehensive comparison of the three major Python version managers. Learn the philosophy, installation process, and use cases for pyenv (Python-only), asdf (polyglot), and mise (Rust-based speed)."
},
{
title: "Migrate from pyenv to mise: Complete Guide for Python 3.12 to 3.13 Upgrades",
url: "/articles/migrate-from-pyenv-to-mise-complete-guide-for-python-3-12-to-3-13-upgrades",
description: "Considering moving from pyenv to mise? This step-by-step migration guide covers uninstalling pyenv, installing mise, preserving configuration, and upgrading to Python 3.13 with minimal downtime."
},
{
title: "uv pip sync: Reproducible Python Environments Without Virtual Environment Overhead",
url: "/articles/uv-pip-sync-reproducible-python-environments-without-virtual-environment-overhead",
description: "Tired of venv overhead? uv pip sync delivers exact, reproducible environments at 10-100x pip's speed—without requiring virtual environments. Ideal for global tooling and system Python workflows."
},
{
title: "Dependency Management: pip, uv, poetry, requirements.txt",
url: "/articles/dependency-management-pip-uv-poetry-requirements-txt",
description: "Master Python dependency management. Compare requirements.txt with pip, Poetry's lockfile approach, and uv's ultra-fast resolver. Learn which tool fits your project's reproducibility and performance needs."
},
{
title: "Fix python: command not found After Installing Python 3.13 with asdf",
url: "/articles/fix-python-command-not-found-after-installing-python-3-13-with-asdf",
description: "Just installed Python 3.13 with asdf but 'python' won't run? This troubleshooting guide covers shim setup, PATH configuration, and common pitfalls that cause 'command not found' errors after installation."
}
]
</script>
<RelatedLinks {relatedLinks} /> Sponsored by Durable Programming
Need help maintaining or upgrading your Python application? Durable Programming specializes in keeping Python apps secure, performant, and up-to-date.
Hire Durable Programming