Skip to content

ci: recompile requirements locks on Dependabot branches - #64

Merged
haasonsaas merged 1 commit into
mainfrom
ci/dependabot-lockfile-refresh
Sep 2, 2026
Merged

haasonsaas merged 1 commit into
mainfrom
ci/dependabot-lockfile-refresh

Conversation

@haasonsaas

Copy link
Copy Markdown
Contributor

Two independent defects keep every Dependabot pull request here red. Both are fixed in this PR.

Defect 1: the lock-sync gate can never pass for Dependabot

.github/workflows/ci.yml lines 22-54 fail the build if requirements.txt changes without requirements.lock, or requirements-dev.txt without requirements-dev.lock.

Dependabot's pip ecosystem edits only the .txt manifests. It cannot regenerate these locks. In dependabot-core, python/lib/dependabot/python/pip_compile_file_matcher.rb treats a file as a pip-compile output only when name.end_with?(".txt") and a sibling .in manifest or an --output-file=<name> header marker exists. A file named requirements.lock is never fetched as a lockfile at all.

#59 and #63 both die on this:

::error::requirements.txt changed without updating requirements.lock

Fix

New .github/workflows/dependabot-lockfiles.yml. On Dependabot pull requests it:

  1. recompiles both locks with the exact uv pip compile invocation recorded in their header comments,
  2. installs from the recompiled requirements-dev.lock and runs black --check ., ruff check ., pytest -q,
  3. commits and pushes the refreshed locks back to the pull request branch.

No --upgrade is passed, so uv reads the existing output file as preferences and only the pins the manifest change actually forces will move.

The job is gated on github.event.pull_request.user.login == 'dependabot[bot]' and on the head branch living in this repository. It is the only place holding contents: write; the workflow default stays contents: read.

ci.yml's lock-sync step now skips Dependabot pull requests, because the new workflow satisfies that invariant for them by construction and runs a strictly larger check. It is untouched for every human pull request.

Defect 2: ruff check . redefines itself on a ruff upgrade

There was no ruff configuration in the repository, so ruff check . ran whatever ruff's built-in default selection happened to be. That default changed in ruff 0.16. On the current tree, with no source changes:

ruff Result
0.15.21 All checks passed!
0.16.4 Found 124 errors. — 48 UP006, 36 BLE001, 17 I001, 10 UP045, 7 UP035, and others

So #63, which bumps ruff 0.15.21 -> 0.16.4, would still fail after the lockfile problem is fixed.

Fix

ruff.toml pins select = ["E4", "E7", "E9", "F"] — the rule set this repository has actually been enforcing. That makes the existing contract explicit rather than letting a tool upgrade rewrite it. Adopting the additional rules remains available as a deliberate, separate change.

How I verified

Check Result
actionlint on both workflow files clean
Recompiling both locks on current main no-op apart from the uv version string in the header; every pin preserved
Simulated #63 on main, then recompiled exactly six pins move in each lock: gunicorn, mypy, python-dotenv, ruff, twilio, typer
Python 3.11 venv from the recompiled requirements-dev.lock: black --check . pass, 24 files
same venv: pytest -q pass, 30 passed
same venv: ruff check . with the new ruff.toml, under ruff 0.15.21 and 0.16.4 pass in both

Unblocks

#59 and #63.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XpuXXVrWCZk3Tq5NRXejNP

Two independent defects keep every Dependabot pull request in this
repository red. Both are fixed here.

1. The lock-sync gate can never pass for Dependabot.

.github/workflows/ci.yml requires requirements.lock to change whenever
requirements.txt changes, and requirements-dev.lock whenever
requirements-dev.txt changes. Dependabot's pip ecosystem edits only the
.txt manifests. It cannot regenerate these locks: dependabot-core treats
a file as a pip-compile output only when the name ends in .txt
(python/lib/dependabot/python/pip_compile_file_matcher.rb, which checks
`name.end_with?(".txt")` and looks for a sibling .in manifest). Files
named requirements.lock and requirements-dev.lock are never fetched as
lockfiles at all. #59 and #63 both fail on this.

.github/workflows/dependabot-lockfiles.yml now recompiles both locks on
Dependabot branches using the exact uv command recorded in their headers,
runs `uv pip install`, `black --check .`, `ruff check .` and `pytest -q`
against the recompiled result, and pushes the refreshed locks back to the
pull request branch. No --upgrade is passed, so uv reads the existing
output file as preferences and only the pins the manifest change forces
will move. The job is gated on
`github.event.pull_request.user.login == 'dependabot[bot]'` and on the
head branch living in this repository, and it is the only place that
holds `contents: write`.

The ci.yml lock-sync step now skips Dependabot pull requests, because the
new workflow satisfies that invariant for them by construction and runs a
strictly larger check. It is unchanged for every human pull request.

2. `ruff check .` silently redefines itself on a ruff upgrade.

There was no ruff configuration in the repository, so `ruff check .` ran
whatever ruff's built-in default selection happened to be. That default
changed in ruff 0.16. On the current tree:

  ruff 0.15.21: All checks passed!
  ruff 0.16.4:  Found 124 errors.
                (48 UP006, 36 BLE001, 17 I001, 10 UP045, 7 UP035, ...)

So #63, which bumps ruff 0.15.21 -> 0.16.4, would still fail after the
lockfile problem is fixed. ruff.toml now pins
`select = ["E4", "E7", "E9", "F"]`, which is the rule set this repository
has actually been enforcing. This makes the existing contract explicit
instead of letting a tool upgrade rewrite it. Adopting the additional
rules stays available as a deliberate, separate change.

Verified locally:
- `actionlint .github/workflows/dependabot-lockfiles.yml
  .github/workflows/ci.yml` is clean.
- Recompiling both locks on the current main is a no-op apart from the uv
  version string in the header comment; every pin is preserved.
- Simulating #63 on top of main (its requirements.txt and
  requirements-dev.txt applied, then both locks recompiled) moves exactly
  six pins in each lock -- gunicorn, mypy, python-dotenv, ruff, twilio,
  typer -- and nothing else. In a Python 3.11 venv from the recompiled
  requirements-dev.lock: `black --check .` passes (24 files),
  `pytest -q` passes (30 passed), and `ruff check .` passes with the new
  ruff.toml under both 0.15.21 and 0.16.4.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpuXXVrWCZk3Tq5NRXejNP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant