uv run is not a bare exec. Before running the command it locks the project if needed and syncs .venv to match pyproject.toml and uv.lock, which means it may install, upgrade, or remove packages as a side effect of what looked like a read-only invocation.
That is usually what you want locally and rarely what you want in CI or in a container entrypoint, where an unexpected re-lock turns a deterministic build into a network call. --frozen runs without checking or updating the lockfile, --locked fails if the lockfile is not already up to date with pyproject.toml, and --no-sync skips touching the environment entirely.
The pairing worth memorising is --locked in CI, so a stale lock is a build failure rather than a silent re-resolution, and --no-sync in a production image where the environment was installed at build time. Also note that uv run prepends the project venv to PATH for the child process, so a script that shells out to python gets the project interpreter even without activation.