# What Happens When You Run `uv run`


[uv](https://pydevtools.com/handbook/reference/uv.md) doesn't replace Python. It orchestrates Python. When you type `uv run pytest`, uv sets up the right environment and then hands off to a real Python interpreter to do the actual work. The command is equivalent to activating a virtual environment and running `python -m pytest` inside it, except uv handles every setup step automatically.

That setup involves finding your project, choosing a Python interpreter, creating or reusing a virtual environment, checking the lockfile, and syncing packages. Each step short-circuits if the work is already done, which is why `uv run` feels instant on repeated invocations but takes longer the first time.

That sequence explains most `uv run` surprises: unexpected downloads, new virtual environments, and lockfile changes you didn't ask for.

Two concepts come up repeatedly in the steps below. A [virtual environment](https://pydevtools.com/handbook/explanation/what-is-a-virtual-environment.md) is an isolated folder (`.venv`) containing a Python interpreter and installed packages, kept separate from the rest of the system so different projects don't interfere with each other. A [lockfile](https://pydevtools.com/handbook/explanation/what-is-a-lock-file.md) (`uv.lock`) records the exact versions of every package so that everyone on the team gets identical installs.

## Step 1: Discover the project

uv starts by searching for a [pyproject.toml](https://pydevtools.com/handbook/reference/pyproject.toml.md) file. It looks in the current working directory first, then walks up through parent directories until it finds one. The first `pyproject.toml` it encounters becomes the project root.

If uv doesn't find a `pyproject.toml`, it runs without project context. In that case it uses an active virtual environment if one exists; otherwise it searches for a Python interpreter and runs the command directly.

You can pass `--no-project` to skip project discovery entirely, or `--project <path>` to point uv at a specific directory instead of searching.

## Step 2: Resolve a Python interpreter

With the project identified, uv needs a [Python interpreter](https://pydevtools.com/handbook/explanation/what-is-a-python-interpreter.md) that satisfies the project's constraints. It checks three sources, in order of priority:

1. The `--python` flag, if passed on the command line (e.g., `uv run --python 3.12 pytest`)
2. A `.python-version` file, searched from the project root upward. `uv init` creates one of these by default
3. The `requires-python` field in `pyproject.toml`, which defines the Python versions the project supports

Once uv has a version constraint, it searches for a matching interpreter. It checks managed installations first (interpreters uv itself has downloaded), then falls back to interpreters on `PATH`.

If no compatible interpreter exists and automatic downloads are enabled (the default), uv downloads and installs one. This is why `uv run` works on a machine with no Python installed at all.

## Step 3: Create the virtual environment

uv places a virtual environment at `.venv` next to the project's `pyproject.toml`. If `.venv` already exists and uses a compatible Python version, this step is a no-op.

If the project defines a `[build-system]` in its `pyproject.toml` (typical for libraries), uv installs the project itself into the environment as an [editable install](https://pydevtools.com/handbook/explanation/what-is-an-editable-install.md). This means code changes take effect immediately without re-running `uv sync`. Application projects created with `uv init` (no `[build-system]`) skip this step.

## Step 4: Lock dependencies

uv checks whether `uv.lock` exists and whether it matches the current project metadata. If the lockfile is missing or stale, uv resolves all dependencies and writes or updates `uv.lock`, the same operation as running `uv lock` manually. If the lockfile is current, uv skips resolution entirely.

During resolution, uv prefers versions already recorded in `uv.lock`. Existing packages stay pinned unless the current constraints exclude them or you pass `--upgrade`.

In CI, you can pass `--locked` to make uv error if the lockfile is out of date (instead of silently updating it), or `--frozen` to skip the freshness check altogether.

## Step 5: Sync the environment

With the lockfile settled, uv installs any packages that are in `uv.lock` but missing from `.venv`. This automatic sync prevents the common "I pulled new code but forgot to install the new dependency" problem.

You can pass `--no-sync` to skip this step if you know the environment is already correct.

## Step 6: Execute the command

With the environment ready, uv runs the command. For tools installed as console scripts (like `pytest`), uv runs the entry point directly. For `uv run python script.py`, it launches the Python interpreter. Either way, uv steps aside once the child process starts. From this point on, your code runs in ordinary Python with no uv involvement.

### Adding one-off packages with `--with`

The `--with` flag installs additional packages into a temporary cached environment without modifying the project's `.venv`. This is useful for trying a package without adding it to `pyproject.toml`:

```bash
uv run --with httpx python -c "import httpx"
```

## Standalone scripts bypass the project pipeline

The steps above apply when `uv run` operates inside a project. When the target is a `.py` file containing [PEP 723 inline script metadata](https://pydevtools.com/handbook/explanation/what-is-pep-723.md), uv skips the project pipeline entirely. It reads the script's `dependencies` and `requires-python`, creates a cached environment for that script, installs the declared dependencies, and runs the file.

```python
# /// script
# dependencies = ["rich"]
# requires-python = ">=3.12"
# ///

from rich import print
print("[bold green]Hello from a script![/bold green]")
```

```bash
uv run script.py
```

The script's environment is isolated from any project. Even if you run the script from inside a uv project directory, the project's dependencies are not available unless the script declares them.

For more on writing scripts with inline metadata, see [How to write a self-contained Python script using PEP 723](https://pydevtools.com/handbook/how-to/how-to-write-a-self-contained-script.md).

## Learn More

- [uv](https://pydevtools.com/handbook/reference/uv.md) reference page
- [uv: A Complete Guide](https://pydevtools.com/handbook/explanation/uv-complete-guide.md) covers broader uv workflows
- [When to use uv run vs uvx](https://pydevtools.com/handbook/explanation/when-to-use-uv-run-vs-uvx.md) explains when to reach for `uv run` versus `uvx`
- [How to use a uv lockfile for reproducible Python environments](https://pydevtools.com/handbook/how-to/how-to-use-a-uv-lockfile-for-reproducible-python-environments.md) goes deeper on lockfile workflows
- [Running commands in projects (uv documentation)](https://docs.astral.sh/uv/concepts/projects/run/) covers `uv run` flags and behaviors in full
