CI Dependency Lists Drift from pyproject.toml

When CI workflows hand-maintain `pip install` commands that duplicate what `pyproject.toml` already declares, the two lists will drift. New dependencies added to `pyproject.toml` will be missing in CI, causing build failures that can't be reproduced locally. The fix is to use `pip install .` so `pyp...

Tags

CI Dependency Lists Drift from pyproject.toml

The Lesson

When CI workflows hand-maintain pip install commands that duplicate what pyproject.toml already declares, the two lists will drift. New dependencies added to pyproject.toml will be missing in CI, causing build failures that can't be reproduced locally. The fix is to use pip install . so pyproject.toml is the single source of truth.

Context

A Python + Node.js project had two CI workflows (build-deploy and PR-check) and two pyproject.toml files (root for pipeline scripts, backend/ for the FastAPI app). Each workflow had pip install commands listing packages by name without version constraints. The root pyproject.toml declared the same packages with version constraints. The backend's pyproject.toml was not referenced by CI at all — the PR-check workflow had its own inline pip install fastapi uvicorn pydantic chromadb ollama httpx.

What Happened

  1. Phase 1 created pyproject.toml with python-frontmatter>=1.1, python-slugify>=8.0, PyYAML>=6.0 and dev extras for pytest>=8 and ruff>=0.11.
  2. Phase 6 created CI workflows. Instead of pip install ., the workflows used pip install python-frontmatter python-slugify PyYAML — no version constraints, manually duplicated.
  3. The PR-check workflow additionally installed backend dependencies inline: pip install fastapi uvicorn pydantic chromadb ollama httpx — again without version constraints and without referencing backend/pyproject.toml.
  4. A structured code review flagged this as finding F-03 (High severity). The workflows would silently break any time a new dependency was added to either pyproject.toml — the developer would add it locally, tests would pass, and CI would fail on a missing import.
  5. The recommended fix: replace pip install <list> with pip install . for the root project and pip install ./backend[dev] for the backend, making pyproject.toml the single source of truth.

Key Insights

Examples

Before (fragile):

# build-deploy.yml
- run: pip install python-frontmatter python-slugify PyYAML

# pr-check.yml
- run: pip install python-frontmatter python-slugify PyYAML pytest ruff
- run: |
    pip install fastapi uvicorn pydantic chromadb ollama httpx
    cd backend && pytest tests/

After (single source of truth):

# build-deploy.yml
- run: pip install .

# pr-check.yml
- run: pip install .[dev] ./backend[dev]

Applicability

This applies to any project where CI installs Python dependencies. It also applies analogously to Node.js projects that use npm install <package> in CI instead of npm ci (which reads package-lock.json). It does NOT apply to projects without a pyproject.toml or setup.py — scripts that are truly standalone may need explicit pip install commands.

Related Lessons

Related Lessons