Featured image of post Setting up a uv-first Python workflow for Claude Code and Codex

Setting up a uv-first Python workflow for Claude Code and Codex

I kept noticing coding agents drifting into `pip install`, raw `python`, and hand-edited dependency files in Python projects. So I built a small repo with reusable `CLAUDE.md` and `AGENTS.md` guidelines that push both Claude and Codex toward a consistent `uv` workflow.

One thing that made this more visible recently: agents don’t only use Python when you explicitly ask them to write Python code.

They also spin up small Python snippets in the background — for example, to scrape a page, preprocess some data, or extract content before feeding it into a prompt (e.g. parsing a PDF). And those “hidden” execution paths are just as prone to drifting into pip install and system Python.

That’s exactly where inconsistency becomes harder to notice — and harder to debug.

I’ve been using coding agents for Python work long enough to notice a recurring pattern: without explicit guidance, they tend to fall back to mixed workflows.

And in Python, that usually means some combination of pip install, raw python script.py, ad hoc virtual environments, and occasionally hand-editing dependency declarations. Technically workable, but inconsistent across runs.

Sometimes it even mixed uv and pip in the same task, which is where things got confusing.

So I put together a small reference repo with reusable instruction files for both Claude and Codex:

github.com/ipeterfulop/coding-agents-uv-setup

The whole point is simple: if a Python project uses uv, then the agent should use it consistently — both for explicit tasks and for any background Python it spins up along the way.

The problem is ambiguity

In practice, most agent mistakes around Python tooling come from ambiguity in project expectations.

If a repository doesn’t make its expectations explicit, the agent has to guess:

  • should it use pip or uv?
  • should it run a script with python or uv run python?
  • should it update pyproject.toml manually or add dependencies through a command?
  • should test and lint commands run directly or through the project environment?
  • what should it do for small, supporting Python tasks in the background?

If you leave those questions open, you get inconsistent behavior. And inconsistent behavior is exactly what you do not want when you’re using an agent repeatedly across a codebase.

What I put in the repo

This repo is not an example application. It’s a small policy kit for coding agents.

It contains two instruction tracks:

  • a Claude-oriented version built around CLAUDE.md
  • a Codex-oriented version built around AGENTS.md

The repo includes:

  • /.claude/CLAUDE.md
  • /.claude/README.md
  • /.codex/AGENTS.md
  • /.codex/README.md

The policy is the same in both: use uv as the single source of truth for Python environment management, package changes, and command execution — regardless of whether the Python code is part of the project or just a short-lived helper script.

That means the agent should prefer commands like:

1
2
3
4
5
6
uv add httpx
uv add --dev pytest
uv sync
uv run python script.py
uv run pytest
uv run ruff check .

And it should avoid the usual drift:

1
2
3
4
pip install httpx
python script.py
pytest
uv pip install ...

What this changes in practice

Without local instructions, the same task might result in:

  • pip install polars
  • running python script.py
  • editing pyproject.toml manually
  • or installing a library ad hoc just to parse a PDF or scrape content

With the policy files in place, the same task consistently results in:

  • uv add polars
  • uv run python script.py
  • no manual dependency edits
  • and the same uv workflow even for those small, supporting scripts

The difference is not correctness — it’s consistency across runs.

Why I split Claude and Codex instructions

One thing I’ve learned from using different coding agents is that “same policy” does not mean “same file.”

Claude Code and Codex both benefit from local repository instructions, but they don’t consume them in exactly the same way. The structure, phrasing, and emphasis that works well in one tool is not always the best fit for the other.

So instead of forcing one generic document onto both, I wrote two versions that are aligned in intent but adapted to their target environment.

The important part is not textual sameness. The important part is behavioral sameness.

If I ask either tool to add a dependency, run a script, install test tooling, execute linting, or just use Python as a helper step, I want the answer to stay inside the uv workflow.

What the instructions actually tell the agent

The bundled files are intentionally strict.

They tell the agent that:

  • uv is the only supported Python package and environment manager
  • dependencies should be changed with uv add, uv remove, uv sync, and uv lock
  • execution should go through uv run
  • one-off tools can go through uvx
  • manual virtualenv creation is off-limits
  • pip and uv pip install are not the expected default for normal project dependency management
  • dependency lists in pyproject.toml should not be edited by hand

Vague guidance like “prefer uv when possible” is usually not enough. In my experience, agents behave more reliably when the repository policy is explicit.

The simplest use case

The practical workflow is intentionally simple.

If you’re using Claude Code, copy the bundled CLAUDE.md into the target repository root.

If you’re using Codex, copy the bundled AGENTS.md into the target repository root.

Then give the agent a task that forces both dependency management and execution. The smoke test I used is intentionally simple:

Add polars to the project, create a small script that groups sales by city, and run it. Do not use pip.

If the instructions are doing their job, the agent should reach for:

1
2
uv add polars
uv run python script.py

If it reaches for pip install, raw python, or manual dependency edits, the instructions are still too weak.

Protecting yourself against compromised packages

One additional guardrail I included in the instructions is uv’s exclude-newer setting.

The idea is simple: newly published packages carry more risk than those that have been around long enough for the community to notice problems. A malicious upload might sit on PyPI for hours or days before anyone flags it. By telling uv to ignore packages published in the last week, you give the ecosystem time to catch and yank bad releases before they land in your project.

The setting goes in pyproject.toml:

1
2
[tool.uv]
exclude-newer = "14 days"

This is not a complete supply chain defense, but it’s a low-friction default that reduces exposure to the most opportunistic attacks. And because it’s declarative and lives in the project file, it applies consistently — whether a human or an agent runs uv add.

The instructions tell the agent to add this setting when scaffolding a new project or updating an existing [tool.uv] table, unless the user explicitly opts out.

A small repo, but a useful one

This isn’t a big framework or a complicated setup. It’s just a small, reusable repo that answers a narrow question:

How do I tell Claude and Codex to stop diverging from the intended Python workflow and consistently use uv — even for the small, background Python tasks they generate along the way?

For me, the answer was to make the rule set explicit, keep it close to the repo, and write the guidance in the format each tool actually understands.

If you’re running coding agents in Python projects and want more predictable behavior, you can grab the instruction files from the repo here:

github.com/ipeterfulop/coding-agents-uv-setup

Built with Hugo
Theme Stack designed by Jimmy