teachyou.ai academy
← All posts
Claude CodePythonAI coding assistantpytestdeveloper tools

Using Claude Code on Python Projects

Pramod Dutta · Jul 6, 2026 · 13 min read

Claude Code works well on Python projects once you give it the same context a new teammate would need: how you manage dependencies, how you run tests, and which commands are safe to run without asking. This guide walks through setting up claude code python workflows from a clean install to a repeatable loop of edit, test, and commit. It covers virtual environments, linting, permission scoping, and the small conventions that make Claude Code noticeably faster on Python repos than on a repo with no guardrails at all.

Installing Claude Code and Pointing It at a Python Repo

Claude Code runs as a CLI you install once per machine, then invoke from inside any project directory. On a Python project, that means cd-ing into the repo root, the same folder that holds pyproject.toml, setup.py, or requirements.txt, and starting the CLI there. Claude Code reads the directory tree from that point down, so starting it one level too high (say, a monorepo root instead of the package folder) makes it read a lot of irrelevant code before it gets useful.

A typical first session looks like this:

cd my-python-service
claude

From there you can ask it to explain the codebase, find where a function is defined, or make a change. The first few minutes matter: Claude Code will explore the repo using its own judgment unless you tell it what matters. For Python specifically, that means telling it up front whether you use pip, poetry, uv, or conda, because the wrong assumption leads to it trying to run pip install in a project that expects poetry install, or vice versa.

If your project has multiple Python packages (a backend/ and a worker/, for instance), it is usually better to start separate Claude Code sessions from each package root rather than one session at the monorepo root. Each session builds its own mental model of the code it can see, and a narrower starting point means faster, more accurate answers.

Writing a CLAUDE.md for a Python Project

The single highest-leverage thing you can do for claude code python work is write a CLAUDE.md file at the repo root. Claude Code reads this file automatically at the start of every session in that directory, so it functions as a standing brief instead of something you repeat by hand every time.

A useful CLAUDE.md for a Python project answers questions a competent engineer would ask on day one:

# Project: payments-service

## Environment
- Python 3.12, dependencies managed with `uv`
- Activate with `uv venv && source .venv/bin/activate`
- Install deps with `uv sync`

## Running things
- Tests: `uv run pytest`
- Single test: `uv run pytest tests/test_orders.py::test_refund -v`
- Lint: `uv run ruff check .`
- Format: `uv run ruff format .`
- Type check: `uv run mypy src/`

## Conventions
- All new code needs type hints
- Use `httpx`, not `requests`, for outbound calls
- Database access goes through `src/db/repository.py`, never raw SQL in route handlers
- Tests live next to the module they cover, under `tests/`

## Do not
- Do not modify anything under `migrations/` without asking first
- Do not add new dependencies without checking `pyproject.toml` first

This does two things. It removes guesswork, Claude Code will run uv run pytest instead of guessing at python -m pytest or a bare pytest that might hit the wrong interpreter. And it encodes house rules that would otherwise only live in a senior engineer's head, like the "no raw SQL in route handlers" rule above.

Keep the file short. A CLAUDE.md that reads like a full engineering handbook gets skimmed the same way a human would skim it. Put the commands and hard rules at the top, and link out (in prose, not as a clickable link) to longer docs if you have them.

Managing Virtual Environments Without Fighting the Agent

Python's dependency story is more fragmented than most languages, and this is where claude code python sessions go wrong most often if you do not set expectations. Claude Code can and will run shell commands, including pip install, python -m venv, or uv sync, when it decides a task needs a package installed. Left completely unscoped, it might install a package globally, activate the wrong virtual environment, or install into a .venv that your IDE is not pointed at.

Three things fix this reliably:

  1. State the exact environment manager in CLAUDE.md, as shown above. Ambiguity is the root cause of most wrong-environment problems.
  2. Keep a single .venv (or equivalent) per project, and make sure it is the one already active in your terminal before you start claude. Claude Code inherits the shell environment it was launched from, so if you activate the virtual environment first, most commands it runs will use it automatically.
  3. If you use uv, prefer uv run <command> over source .venv/bin/activate && command in your documented commands. uv run resolves the right interpreter without depending on shell activation state, which makes it more reliable across the different shells Claude Code might invoke commands in.

For projects still on plain pip and venv, put the exact activation line in CLAUDE.md and repeat it in your prompt if you are asking Claude Code to run something unusual, like a one-off script or a data migration.

Running Tests and Linters as Part of the Loop

The most reliable way to use Claude Code on a Python codebase is to make verification part of every change, not a separate step you do afterward. Ask it directly to run the test suite after an edit, and to keep iterating until the tests pass, rather than asking it to "make the change" and checking yourself later.

A workflow that holds up well in practice:

  • Ask for a specific, scoped change ("add a cancel_subscription method to BillingService that also refunds the current period, with a test").
  • Let Claude Code write the code and the test together, not the code first with tests promised later.
  • Have it run pytest (or your project's documented test command) and read the failure output itself, rather than pasting failures back manually.
  • Once green, have it run your linter and formatter (ruff check ., ruff format ., or black . and flake8 if that is your stack) before you review the diff.

For type-checked codebases, add mypy or pyright to that same loop. Claude Code is good at reading a type error and understanding what it implies about a function signature, which is often faster than a human tracing the same error through several call sites.

Example of the kind of instruction that produces a tight loop:

Add pagination to the `list_orders` endpoint in src/api/orders.py.
Use limit/offset query params, default limit 50, max 200.
Write a test in tests/test_orders.py covering the default, a custom
limit, and the max-limit clamp. Run pytest and ruff after, and fix
anything that fails before showing me the diff.

That single prompt gives Claude Code the scope, the acceptance criteria, and the verification step, which is usually enough for it to complete the task without a back-and-forth.

Refactoring and Debugging Existing Python Code

Claude Code is particularly useful on the kind of Python code that accumulates in real projects: a service module that grew past its original design, a script with inconsistent error handling, or a test file that got copy-pasted one too many times. For refactors, give it the boundary explicitly. "Extract the retry logic in client.py into a decorator, keep the public API of fetch_with_retry the same" gives it a clear target and a constraint that stops it from rewriting more than you asked for.

For debugging, the more useful pattern is to hand it the actual failure, not a description of it. Paste the traceback, the failing test name, or the exact error message, and ask it to find the root cause before proposing a fix. Python tracebacks carry a lot of structure (the call chain, the exact line, the exception type) and Claude Code reads that structure well when it is given the raw text instead of a paraphrase.

pytest tests/test_billing.py -k test_partial_refund

If that fails, pasting the full traceback plus "find the root cause, don't just patch the symptom" produces noticeably better fixes than a vague "this test is broken, can you fix it."

For flaky or environment-dependent Python bugs (a test that passes locally but fails in CI, a datetime that behaves differently under a different timezone, a dependency version mismatch), tell Claude Code what differs between the two environments. It cannot infer CI configuration it has not seen, so pointing it at the CI config file (.github/workflows/test.yml or similar) alongside the failure gets to the answer faster than describing the symptom in prose.

Working with uv, ruff, mypy, and pytest Together

Python tooling has consolidated a lot around a handful of fast, well-behaved tools, and Claude Code handles them cleanly once they are named in CLAUDE.md. A few notes specific to each:

uv: Fast, and it manages both the virtual environment and the lockfile, which removes a class of "works on my machine" problems. If you use uv, tell Claude Code to use uv add <package> instead of editing pyproject.toml by hand, since uv add also updates the lockfile and installs in one step.

ruff: Handles both linting and formatting, replacing what used to be a flake8 + black + isort combination. Because it is fast, it is cheap to run after every change, so it is worth including in the exact command list in CLAUDE.md rather than leaving it as a manual step you run at the end.

mypy: Slower than ruff, and more prone to producing errors that are technically correct but not what you actually want fixed (a third-party library with incomplete stubs, for instance). If your project has a mypy.ini or a [tool.mypy] section with exclusions, make sure Claude Code has seen it; otherwise it may "fix" a type error by weakening a type hint instead of adding the right stub or ignore comment.

pytest: The de facto standard, and Claude Code writes idiomatic pytest tests (fixtures, parametrize, monkeypatch) without much prompting if the existing test suite already uses those patterns. If your suite still uses unittest.TestCase classes, say so explicitly, or you will end up with a mix of styles in the same file.

A combined command for a uv-based project, useful to put directly in CLAUDE.md:

uv run ruff check . && uv run ruff format --check . && uv run mypy src/ && uv run pytest

Running all four in sequence gives Claude Code a single command that tells it, in one shot, whether a change is done or not.

Scoping Permissions for Python Commands

Claude Code asks for permission before running commands it has not been told are safe, and Python projects have a specific shape of command that is worth pre-approving deliberately rather than approving one at a time mid-session. Project-level settings let you allow a list of command patterns so routine commands (running tests, running the linter, installing already-declared dependencies) do not interrupt the flow, while anything unusual (deleting files, force-pushing, running a database migration against a real database) still stops and asks.

A reasonable starting allowlist for a Python project includes the test runner, the linter and formatter, and the dependency sync command, since none of those mutate anything outside the project's own virtual environment or its own files. Commands that touch external systems (deploy scripts, database migrations, anything that calls a real API with real credentials) are worth leaving un-allowlisted on purpose, so a human reviews them each time even if Claude Code proposes running them as part of a larger task.

It is also worth being deliberate about .env files and secrets. Claude Code will read files in the project directory when it needs to, including a .env file if nothing tells it not to. Add .env to whatever ignore mechanism your project setup supports for the agent, the same way you would .gitignore it, so a session summarizing "what's in this repo" does not surface API keys in its output.

A Realistic End-to-End Workflow

Putting the pieces together, a full task on a Python project might look like this from start to finish:

  1. Start Claude Code from the package root with the virtual environment already active.
  2. Confirm CLAUDE.md is current, especially the command list, since a stale command (a renamed test path, for example) wastes a round trip.
  3. Describe the task with a concrete acceptance test: what should be true when it is done, not just what file should change.
  4. Let Claude Code make the change, run the test suite and linter itself, and iterate on failures before presenting a diff.
  5. Review the diff. For Python specifically, check that type hints were kept consistent, that no bare except: clauses were introduced, and that new dependencies (if any) were added through the proper manager command rather than hand-edited into pyproject.toml.
  6. Ask for a second pass if the diff touches more files than the task implied. This catches the common failure mode where a "small fix" also reformats an unrelated file because a formatter ran over the whole tree instead of the touched files.
  7. Commit with a clear message once you're satisfied, using your own git workflow rather than letting the agent decide commit boundaries on a large change.

This loop scales down to small tasks (a one-line bug fix) and up to larger ones (adding a new module with its own tests), because the verification step, running the real test suite, catches most of the ways an automated change can go subtly wrong.

FAQ

Does Claude Code know which Python version my project uses? It infers this from files like pyproject.toml, .python-version, setup.py, or a Dockerfile, but it is safer to state the version explicitly in CLAUDE.md alongside the dependency manager. Ambiguity here is the most common source of "it installed the wrong thing" complaints.

Will Claude Code activate my virtual environment automatically? No. It runs commands using the shell environment it was started in. Activate your virtual environment before launching Claude Code, or use a tool like uv run that resolves the correct interpreter without relying on shell activation.

Can I use Claude Code with Django or Flask projects, not just plain scripts? Yes. Framework-specific conventions (URL routing, ORM models, migrations) work the same way as any other convention: document them in CLAUDE.md, and be explicit about migration safety, since running a real migration against a real database is exactly the kind of command worth keeping outside the pre-approved allowlist.

How do I stop Claude Code from installing packages I don't want? State your dependency policy in CLAUDE.md ("do not add new dependencies without asking first") and keep the install/sync command out of your always-allowed list if you want a manual approval step before any new package lands in pyproject.toml or requirements.txt.

What is the biggest mistake people make running Claude Code on Python codebases? Skipping the CLAUDE.md file and re-explaining the environment, commands, and conventions from scratch in every session. The file costs a few minutes to write once and pays for itself within the first session by removing the guesswork that leads to wrong-environment and wrong-command mistakes.

Does Claude Code replace running `pytest` and `ruff` myself? No, it runs them for you as part of the loop, but you should still review the diff and, for anything non-trivial, run the suite yourself before merging. Treat its test run as the first pass, not the last one.