Preface¶
A common toolchain for Python projects looks like this: use pip to install dependencies, virtualenv to manage environments, flake8 for linting, black for formatting, isort to sort imports, mypy for type checking, and pre-commit to tie all these into Git. Configurations are often scattered across files like requirements.txt, setup.py, .flake8, and mypy.ini. When coding, agents frequently run pip install by habit or manually execute source .venv/bin/activate.
On the Astral side, package management, linting, and formatting have been consolidated into uv and ruff, and type checking now has its official sibling tool ty. The problem is that while humans can migrate gradually following documentation, agents may still default to using old commands. Trail of Bits has turned the choices from its internal template cookiecutter-python into the Agent Skill modern-python, which standardizes how new projects are set up, old projects are migrated, and single-file scripts declare their dependencies.
What is this¶
modern-python is a Skill published by Trail of Bits on the trailofbits/skills marketplace, authored by William Tan. Its official description is: Configure Python projects with uv, ruff, and ty; use it when creating projects, writing standalone scripts with dependencies, or migrating from pip / Poetry / mypy / black.
It follows the universal SKILL.md format, so it can be loaded into tools that support Agent Skills such as Claude Code, Codex CLI, and Cursor. The repository categorizes it under the Development section, alongside security auditing Skills in the marketplace. The practical specifications come directly from Trail of Bits’ own cookiecutter template, rather than being an unrelated parallel standard.
The Skill clearly states inapplicable scenarios: do not forcefully replace the existing toolchain when users request to keep it; do not use this toolchain when Python 3.11 or lower is required; do not apply this to mixed-language repositories where Python is not the primary language.
Core Tools¶
The Skill lists recommended tools and their replaced legacy counterparts in a table. Cross-referencing the SKILL.md on GitHub with the official documentation confirms the content is consistent:
| Tool | Purpose | Replaces |
|---|---|---|
| uv | Package and dependency management | pip, virtualenv, pip-tools, pipx, pyenv |
| ruff | Linting and formatting | flake8, black, isort, pyupgrade, pydocstyle |
| ty | Type checking | mypy, pyright |
| pytest | Testing and coverage | unittest |
| prek | Git hooks | pre-commit |
ty is developed by Astral, the same team behind uv and ruff. According to Astral’s official documentation, it is a Rust-written type checker currently in beta. The Skill still recommends it as a replacement for mypy / pyright, but you should account for this risk during implementation: rules and diagnostics may still change between minor versions.
prek refers to j178/prek, a Rust-implemented single binary that is compatible with existing .pre-commit-config.yaml files. The Skill positions it as a faster alternative to pre-commit that does not depend on the Python runtime.
In addition to development tools, the Skill includes a set of security-related capabilities, primarily intended to run in pre-commit or CI:
| Tool | Purpose | Execution Timing |
|---|---|---|
| shellcheck | Shell script linting | pre-commit |
| detect-secrets | Secret detection | pre-commit |
| actionlint | GitHub Actions syntax validation | pre-commit, CI |
| zizmor | Workflow security auditing | pre-commit, CI |
| pip-audit | Dependency vulnerability scanning | CI, manual |
| Dependabot | Automated dependency updates | Scheduled |
When installed as a Claude Code plugin, the repository includes a SessionStart hook: it adds PATH shims for python, pip, pipx, and uv. If an agent runs python or pip install directly, the command will be intercepted and the user prompted to use uv run / uv add instead. Commands like grep python or which python are not affected, because python is passed as an argument rather than being the executed command. If you only copy the SKILL.md into the Skill directory, this hook may not be included, depending on the actual installation method.
A Few Hard Conventions¶
The Skill has compiled common anti-patterns into a comparison table, and you should prioritize using the right-hand column recommendations when writing code:
| Don’t Do This | Use Instead |
|---|---|
Writing python-version under [tool.ty] |
Write python-version under [tool.ty.environment] |
uv pip install |
uv add and uv sync |
Manually editing pyproject.toml to add dependencies |
uv add / uv remove |
Using hatchling as the build backend |
uv_build (sufficient for most projects) |
| Poetry | uv |
requirements.txt |
Use PEP 723 for scripts, use pyproject.toml for projects |
| mypy / pyright | ty |
Using [project.optional-dependencies] for development tools |
Use [dependency-groups] (PEP 735) |
source .venv/bin/activate |
uv run |
| pre-commit | prek |
Three core principles are repeated throughout the documentation:
1. Manage dependencies exclusively via uv add / uv remove
2. Always use uv run for commands, do not manually activate virtual environments
3. Place development, testing, and documentation dependencies in [dependency-groups], not in user-facing extras
Installation and Activation¶
Claude Code¶
Installation from the official marketplace has two steps. First, add Trail of Bits’ plugin marketplace:
/plugin marketplace add trailofbits/skills
Then install the modern-python plugin:
/plugin install trailofbits/skills/plugins/modern-python
You can also run /plugin menu first to browse the marketplace before installing. The official Quick Start provides this example invocation:
Use the modern-python skill to create a new Python project with uv, ruff, and pytest
Codex CLI¶
The repository README notes that Codex can directly load Claude’s marketplace without additional sidecar metadata:
codex plugin marketplace add trailofbits/skills
codex plugin list
codex plugin add modern-python@trailofbits
The plugin name in the final command matches the plugins/modern-python directory name in the repository.
Universal Skill Installation (for Cursor and others)¶
The installation command from skills.sh is:
npx skills add https://github.com/trailofbits/skills --skill modern-python
This command follows the universal Agent Skill directory convention to place SKILL.md into the skills path used by your current tool. After installation, you can directly describe your task, for example: “Configure this repository with uv and ruff using the modern-python skill” or “Migrate the existing pip + requirements.txt setup to uv”.
Typical Usage¶
The Skill uses a decision tree to categorize four types of work: single-file scripts follow PEP 723; multi-file projects not intended for distribution use minimal uv configuration; redistributable packages use full project configuration; existing repositories follow the migration guide.
1. Minimal Project¶
For multi-file projects not intended to be published to PyPI, the official Quick Start is as follows:
uv init myproject
cd myproject
uv add requests rich
uv add --group dev pytest ruff ty
uv run python src/myproject/main.py
uv run pytest
uv run ruff check .
Dependencies are stored in pyproject.toml and uv.lock. To temporarily test a package without adding it to permanent project dependencies, use uv run --with:
uv run --with requests python -c "import requests; print(requests.get('https://httpbin.org/ip').json())"
uv run --with httpx pytest
2. Full Package Project¶
When starting from scratch, the Skill will first ask if you want to use Trail of Bits’ cookiecutter to generate a complete scaffold at once, including pyproject.toml, src/ layout, dependency groups, hooks, GitHub Actions, and security scans:
uvx cookiecutter gh:trailofbits/cookiecutter-python
If you do not use the template, use uv to create a distributable package:
uv init --package myproject
cd myproject
The generated directory structure is:
myproject/
├── pyproject.toml
├── README.md
├── src/
│ └── myproject/
│ └── __init__.py
└── .python-version
The key configurations provided in SKILL.md are as follows. Do not manually edit dependency groups; use uv add --group to maintain them:
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = []
[dependency-groups]
dev = [{include-group = "lint"}, {include-group = "test"}, {include-group = "audit"}]
lint = ["ruff", "ty"]
test = ["pytest", "pytest-cov"]
audit = ["pip-audit"]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["ALL"]
ignore = ["D", "COM812", "ISC001"]
[tool.pytest]
addopts = ["--cov=myproject", "--cov-fail-under=80"]
[tool.ty.terminal]
error-on-warning = true
[tool.ty.environment]
python-version = "3.11"
[tool.ty.rules]
possibly-unresolved-reference = "error"
unused-ignore-comment = "warn"
Install dependencies:
uv sync --all-groups
# Or only install a specific group
uv sync --group dev
The Skill also recommends adding a Makefile to the repository to wrap common commands with uv run:
.PHONY: dev lint format test build
dev:
uv sync --all-groups
lint:
uv run ruff format --check && uv run ruff check && uv run ty check src/
format:
uv run ruff format .
test:
uv run pytest
build:
uv build
3. Single-file Scripts: PEP 723¶
When you have a single file that requires third-party libraries, you no longer need to maintain a separate requirements.txt. The Skill’s reference documentation requires embedding metadata in script comments and executing it with uv run:
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests",
# "rich",
# ]
# ///
import requests
from rich import print
response = requests.get("https://httpbin.org/ip")
print(response.json())
uv run script.py
You can also use uv to manage script dependencies:
uv init --script myscript.py
uv add --script myscript.py requests
uv remove --script myscript.py requests
The official documentation also lists limitations: PEP 723 does not support dependency groups, editable installs, or lockfiles, and the resolved package versions may vary between runs. Use a full pyproject.toml if you need these capabilities.
4. Migrating from Legacy Toolchains¶
Only follow this path when the user explicitly requests a migration; if the user wants to keep pip / Poetry / mypy, the documentation requires respecting their existing workflow.
To migrate from requirements.txt + pip to uv:
uv init --bare
# Add dependencies one by one, do not edit pyproject.toml manually
uv add requests rich
# Or import from requirements.txt (complex version constraints may require manual fixes)
grep -v '^#' requirements.txt | grep -v '^-' | grep -v '^\s*$' | while read -r pkg; do
uv add "$pkg" || echo "Failed to add: $pkg"
done
uv sync
Then delete requirements.txt, requirements-dev.txt, and the old virtual environment directories (venv/, .venv/), and commit uv.lock to version control.
When migrating from setup.py / setup.cfg: first run uv init --bare, move dependencies from install_requires using uv add, move development dependencies to uv add --group dev, copy project metadata such as name, version, and description to the [project] section, then delete setup.py, setup.cfg, and MANIFEST.in.
To migrate from flake8 + black + isort to ruff:
uv remove flake8 black isort
# Delete .flake8 and the [tool.black] / [tool.isort] sections in pyproject.toml
uv add --group dev ruff
uv run ruff format .
uv run ruff check --fix .
To migrate from mypy / pyright to ty:
uv remove mypy pyright
# Delete mypy.ini, pyrightconfig.json, and the [tool.mypy] / [tool.pyright] sections
uv add --group dev ty
uv run ty check src/
The documentation mentions that you can migrate from Poetry to uv, but the migration section in SKILL.md does not include separate step-by-step commands for Poetry, only listing Poetry as a tool to replace in the anti-pattern table. For Poetry projects, refer to the references/migration-checklist.md in the repository, do not directly apply the pip migration steps.
Applicable Scenarios and Notes¶
Situations where this Skill is suitable include: creating new Python packages or internal projects; adding pyproject.toml, linting, and testing configurations to existing repositories; writing single-file scripts with third-party dependencies; when users explicitly request migration from pip / flake8 / black / mypy.
The documentation clearly lists unsuitable cases: requiring support for Python versions below 3.11; when users explicitly want to keep Poetry, mypy, or black; when Python is a secondary language in a multi-language repository.
There are several points to note when using this Skill:
1. Minimum Python version is 3.11. requires-python = ">=3.11" and ruff’s target-version = "py311" are paired configurations; do not modify only one of them.
2. ty is still in beta. The Skill uses it as the default type checker, but Astral’s own documentation notes that its API and diagnostics are still unstable. For repositories where type check results are critical, run uv run ty check src/ on a feature branch first to confirm false positives are acceptable.
3. Default coverage threshold is 80%. For old projects adopting pytest-cov for the first time, this threshold will likely cause CI failures. Adjust the value based on the repository’s current state instead of copying it verbatim.
4. ruff’s select = ["ALL"] is strict. The official configuration uses ignore to turn off D (pydocstyle), COM812, and ISC001, while enabling all other rules. Applying this to legacy code will trigger a large number of warnings. You should tighten rules module by module instead of expecting a single --fix to resolve all issues.
5. The plugin hook only works when installed via the marketplace. Claude Code / Codex will intercept bare python/pip commands when installed through the official marketplace; if you only sync the SKILL.md file, agents may still generate old commands. It is best to include instructions in your prompts like “do not use pip, use uv add / uv run instead”.
6. Reference documentation is more detailed than SKILL.md. The same directory includes additional files such as pyproject.md, uv-commands.md, ruff-config.md, testing.md, pep723-scripts.md, prek.md, security-setup.md, dependabot.md, and migration-checklist.md. For full migrations or security scans, ask the agent to reference these files instead of only relying on the summary in the main SKILL text.
Summary¶
What modern-python does is very specific: it turns the toolchain (uv, ruff, ty, pytest, prek) already used in Trail of Bits’ cookiecutter template into executable specifications for agents, and includes migration steps from pip / setup.py / flake8 / mypy. It does not solve the question of “whether Python should be modernized”, but rather addresses scenarios where agents default to running pip install, manually editing pyproject.toml, or activating virtual environments.
Official links:
- Skill directory: https://github.com/trailofbits/skills/tree/main/plugins/modern-python/skills/modern-python
- Plugin documentation: https://github.com/trailofbits/skills/tree/main/plugins/modern-python
- Documentation site: https://trailofbits-skills.mintlify.app/plugins/modern-python
- skills.sh: https://skills.sh/trailofbits/skills/modern-python
- Template repository: https://github.com/trailofbits/cookiecutter-python