Workaround for Claude Code running `python` instead of `uv`

uv is now the de facto default Python package manager. I have already deleted all pythons from my system except for the one that has to be installed for other packages in brew.

Unfortunately, Claude Code often ignores instructions in CLAUDE.md files to use uv run python instead of plain python commands. Even with clear documentation stating “always use uv”, Claude Code will attempt to run python directly, leading to “command not found” errors in projects that rely on uv for Python environment management.

The built-in Claude Code hooks and environment variable settings also don’t reliably solve this issue due to shell context limitations.

The reason is that Claude (and most other AI models) take time to catch up to such changes, because their learning horizon is longer, up to months to years. Somebody will need to include this information explicitly in the training data.

Until then, we can prevent wasting tokens by mapping python and python3 to uv.

I personally don’t want to map these globally, because a lot of other packages might depend on system installed pythons, like brew packages, gcloud CLI and so on.

Because of that, I map them at the project level, using direnv:

An OK-ish solution: direnv + dynamic wrapper scripts

We can force Claude Code (and any developer) to use uv run python by dynamically creating wrapper scripts in a .envrc file that direnv automatically loads when entering the project directory.

This will override python and python3 to map to uv run python, and also print a nice message to the model:

Use "uv run python ..." instead of "python ..." idiot.

This is probably not the best solution, but it is a solution. Feel free to suggest a better one.

Step 1: Install direnv

# macOS
brew install direnv

# Ubuntu/Debian
sudo apt install direnv

# Add to your shell (bash/zsh)
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc  # or ~/.bashrc
source ~/.zshrc  # or restart terminal

Step 2: Setup direnv with dynamic wrapper scripts

# Create .envrc file in project root
cat > .envrc << 'EOF'
#!/bin/bash
# Create temporary bin directory for python overrides
TEMP_BIN_DIR="$PWD/.direnv/bin"
mkdir -p "$TEMP_BIN_DIR"

# Create python wrapper scripts
cat > "$TEMP_BIN_DIR/python" << 'INNER_EOF'
#!/bin/bash
echo "Use \"uv run python ...\" instead of \"python ...\" idiot"
exec uv run python "$@"
INNER_EOF

cat > "$TEMP_BIN_DIR/python3" << 'INNER_EOF'
#!/bin/bash
echo "Use \"uv run python ...\" instead of \"python3 ...\" idiot"
exec uv run python "$@"
INNER_EOF

# Make them executable
chmod +x "$TEMP_BIN_DIR/python" "$TEMP_BIN_DIR/python3"

# Add to PATH
export PATH="$TEMP_BIN_DIR:$PATH"
EOF

# Allow direnv to load this configuration
direnv allow

Step 3: Update .gitignore

# Add direnv generated files to .gitignore
echo "# direnv generated files" >> .gitignore
echo ".direnv/" >> .gitignore

Step 4: Update documentation

Add to your CLAUDE.md something like this:

## Python Package Management with uv

**IMPORTANT**: This project uses `uv` as the Python package manager. ALWAYS use `uv` instead of `pip` or `python` directly.

DO NOT RUN:

```bash
python my_script.py
# OR
chmod +x my_script.py
./my_script.py
```

INSTEAD, RUN:

```bash
uv run my_script.py
```

### Key uv Commands

- **Run Python code**: `uv run <script.py>` (NOT `python <script.py>`)
- **Run module**: `uv run -m <module>` (e.g., `uv run -m pytest`)
- **Add dependencies**: `uv add <package>` (e.g., `uv add requests`)
- **Add dev dependencies**: `uv add --dev <package>`
- **Remove dependencies**: `uv remove <package>`
- **Install all dependencies**: `uv sync`
- **Update lock file**: `uv lock`
- **Run with specific package**: `uv run --with <package> <command>`

How It Works

  1. direnv automatically loads .envrc when you cd into the project directory
  2. .envrc dynamically creates executable wrapper scripts in .direnv/bin/
  3. Scripts display a helpful message and redirect to uv run python
  4. .direnv/bin/ is prepended to PATH, overriding system python commands
  5. Works for any shell session in the directory (Claude Code, terminal, IDE)

To see if it works:

cd your-project/
python -c "print('Hello World')"  # Shows message, uses uv
python3 --version                 # Shows message, uses uv

Let me know if this doesn’t work for you, or if you find a better solution.