# How to configure Claude Code to use uv

[Claude Code](https://claude.com/product/claude-code) already reaches for `uv add` and `uv run` inside a project that has a `pyproject.toml`. Two things still go wrong: it falls back to `python3 script.py` in a directory with no project metadata, and it stops for an approval prompt on every [uv](https://pydevtools.com/handbook/reference/uv.md) command it does run.

Both are observed defaults rather than documented contracts, so both can shift under you. A project `CLAUDE.md` and a permission allowlist put the behavior in your repository instead.

## Prerequisites

* [Claude Code](https://claude.com/product/claude-code) installed
* [uv installed](https://pydevtools.com/handbook/how-to/how-to-install-uv.md) on your system
* A project directory. The allowlist check at the end adds a dependency, so run `uv init` first if the directory has no `pyproject.toml`.

## Pin uv as the only way to run Python

### 1. Write the uv rules into CLAUDE.md

Create `CLAUDE.md` in your project root with these rules. Claude Code loads the file at the start of every session, so the rules survive a restart and reach teammates who clone the repo.

```markdown
# Python tooling

Use uv for every Python command in this project.

- Run scripts and tools with `uv run`, never bare `python` or `python3`.
- Add and remove dependencies with `uv add` and `uv remove`, never `pip install`.
- Sync and lock the environment with `uv sync` and `uv lock`.
- Run a one-off tool without adding it to the project: `uvx ruff check`.
- For a standalone script, use `uv run script.py` and add its dependencies with
  `uv add --script script.py <package>`.
```

### 2. Confirm Claude reaches for uv

Drop a throwaway script in the project root and ask Claude Code to run it:

```bash
echo 'print("hello")' > hello.py
claude -p "Run hello.py and show me the output."
```

Claude runs `uv run hello.py`. In a directory that has neither `pyproject.toml` nor these rules, the same request gets `python3 hello.py`, which is the gap the file closes. Delete `hello.py` once you have seen the difference.

## Stop the approval prompts

### 1. Allowlist the uv commands in .claude/settings.json

Create `.claude/settings.json` to pre-approve the uv commands and deny pip. Commit it so teammates inherit the same prompts, or keep the rules to yourself in `.claude/settings.local.json`.

```json {filename=".claude/settings.json"}
{
  "permissions": {
    "allow": [
      "Bash(uv run:*)",
      "Bash(uv add:*)",
      "Bash(uv remove:*)",
      "Bash(uv sync:*)",
      "Bash(uv lock:*)",
      "Bash(uvx:*)"
    ],
    "deny": [
      "Bash(pip install:*)",
      "Bash(pip3 install:*)",
      "Bash(python -m pip install:*)",
      "Bash(python3 -m pip install:*)"
    ]
  }
}
```

Every rule matches the literal start of a command, so `Bash(uv run:*)` covers `uv run pytest`, `uv run ruff check`, and `uv run python`. That same literal matching is why pip takes four deny rules: block only `pip install` and `python -m pip install` sails through, which is the spelling Claude picks once the short one is refused.

### 2. Accept the workspace trust dialog

Allow rules from a project you have not trusted are skipped, so the file you just wrote does nothing yet. Start an interactive session in the project root and accept the dialog before going further:

```bash
claude
```

`claude -p` skips the trust dialog rather than showing it, so print mode alone can never get you past this point.

### 3. Confirm the allowlist changed what Claude does

Ask Claude Code to add a dependency and watch whether it needs you:

```bash
claude -p "Add idna as a dependency to this project."
```

`uv add idna` runs unattended and `idna` appears under `dependencies` in `pyproject.toml`. Run `uv remove idna` to undo it.

If `dependencies` is unchanged and Claude reports that `uv add idna` needed approval, the rules did not load. Claude Code says so on startup:

```
Ignoring 6 permissions.allow entries from .claude/settings.json: this workspace has not been trusted.
```

Repeat the trust step from the directory the message names. Trust is keyed to the git repository root, or to the directory you started Claude Code in when there is no repository, so accepting the dialog in a subdirectory leaves the root untrusted.

## Apply the rules to every project

Personal defaults that follow you across repositories go in your user-level files, which Claude Code reads regardless of working directory.

```
~/.claude/CLAUDE.md
~/.claude/settings.json
```
```
%USERPROFILE%\.claude\CLAUDE.md
%USERPROFILE%\.claude\settings.json
```
Project files win over user files, so a repository that needs different rules can override them.
