🔧 Add a scheduled prek update workflow - #1806
Merged
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1806 +/- ##
==========================================
+ Coverage 86.87% 91.26% +4.38%
==========================================
Files 56 77 +21
Lines 6532 11761 +5229
==========================================
+ Hits 5675 10734 +5059
- Misses 857 1027 +170
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Replaces the weekly pre-commit.ci autoupdate pull request. pre-commit.ci only reads the root `.pre-commit-config.yaml` with vanilla pre-commit, whereas `prek update` is workspace-aware and updates every config in the tree. Runs monthly (and on demand), applies hook autofixes so formatter churn arrives in the same pull request, and opens it on a fixed branch through the bot app — a pull request pushed with the default GITHUB_TOKEN would not trigger CI, and `Lint` is a required check.
chrisjsewell
force-pushed
the
ci/prek-update
branch
from
September 2, 2026 07:35
58047b4 to
f4843ef
Compare
chrisjsewell
added a commit
that referenced
this pull request
Sep 2, 2026
`uv.lock` has been in `.gitignore` since uv was adopted. This commits it, re-enables the `uv-lock` hook that keeps it honest, and points CI and dependabot at it. ## Why commit the lock A uv lock file is **universal**: one file records the resolution for every operating system, architecture and Python version the project supports, so there is no per-platform lock to maintain and no reason to regenerate it per CI job. It is also **not package metadata**. It is not in the sdist or the wheel, and pip and uv both ignore it when installing `sphinx-needs` as a dependency. **Nothing changes for anyone who installs this package** — the version ranges in `pyproject.toml` remain the contract. What the lock pins is *contributors and CI*, which is exactly what has been unpinned. The practical effect is on the failure mode. Today, when any transitive dependency of the lint environment ships a breaking release, every open pull request goes red at once and somebody has to work out which upstream did it. With the lock committed, the environment stays fixed until it is deliberately changed, so that same release turns one dependabot pull request red instead, in isolation, with the offending bump named in the diff. Scope, to be clear: the lock governs the `Lint` and `Prek update` jobs, which are the only uv-based jobs here. The pytest matrix still installs with pip across `sphinx~=7.4/8.2/9.1` on purpose — testing the *range* is the point there, and that is unchanged. ## What changed **The lock.** `uv.lock` removed from `.gitignore` and committed. It was generated with uv 0.12.9 — the same version the hook below pins, and the same version `astral-sh/setup-uv@v10` installs today — and re-running `uv lock` leaves it byte-identical. Nothing needed a `tool.uv.conflicts` table; it resolves clean at 113 packages. **The `uv-lock` hook, re-enabled.** `.pre-commit-config.yaml` has carried a commented-out `astral-sh/uv-pre-commit` block at `rev: 0.5.5` with `# TODO this does not work on pre-commit.ci`. That blocker is gone — pre-commit.ci is no longer in use and the `Lint` job runs prek — so the block is now live at `rev: 0.12.9`, and the stale comment is deleted. The hook uses its default `files: ^(uv\.lock|pyproject\.toml|uv\.toml)$`, so it only fires when the dependencies actually change; the rest of the time it is a no-op. Change a dependency without re-locking and it fails with the refreshed lock already in the diff. **`--frozen` on every CI uv command** — the `Lint` job's `uv run prek run`, and both `uv run` calls in the `Prek update` workflow. `--frozen` means *use the committed lock verbatim, never re-resolve, and error out if it is missing*. Without it, uv silently re-resolves and writes a new lock when one is absent — no warning, no non-zero exit — which is precisely the drift the lock is meant to remove. Not `--locked`, which additionally errors when the lock is *stale*: that would be a redundant second check, because the `uv-lock` hook already runs in the same `Lint` job via prek and fails on a stale lock, with a more useful message and the fix in the diff. **Dependabot: `pip` → `uv`.** The `pip` ecosystem reads `pyproject.toml` and proposes bumps to the `~=` pins in the extras. Nobody merges those — #1431, #1415 and #1377 have been open for the best part of a year. The `uv` ecosystem instead refreshes `uv.lock`, which is what now needs refreshing. Same `directory`, same monthly schedule, same `⬆️` prefix, plus one group: **minor and patch updates are batched into a single monthly pull request, while major updates fall outside the group and arrive one per dependency.** Breaking changes concentrate in majors, so the bumps most likely to go red are the ones that come isolated, and the routine churn does not become one PR per transitive dependency (the `uv` ecosystem tracks indirect dependencies too). If a batch ever goes red on a 0.x "minor" that is really a major, the escape hatch is `exclude-patterns` on the group — dependabot then rebuilds the batch without it and that dependency gets its own pull request. The `github-actions` stanza is untouched. > Please close #1431, #1415 and #1377 after this merges — they are pip-ecosystem pull > requests that nothing will update any more. (#1360 and #1197 are `github-actions` pull > requests and are *not* affected by this change; #1360 in particular is worth merging on > its own, since `codecov/codecov-action@v3` is old enough that actionlint flags it.) **Docs.** `docs/contributing.rst` gains a short paragraph next to `uv sync`: the lock is committed so `uv sync` installs exactly those versions, edit `pyproject.toml` and let the `uv-lock` hook (or `uv lock`) update it, and dependabot refreshes it monthly. No minimum uv version is called out because none is needed — uv 0.9.24 reads this lock and agrees it is up to date, so no one has to upgrade uv for this. ## What the lock resolves to `sphinx` **7.4.7** and `docutils` **0.20** — one version each, no per-marker split. That is not uv being conservative: the `mypy` dependency-group pins `sphinx==7.4.7` and `docutils==0.20` exactly, and a universal resolution has to satisfy every group and extra at once, so those two `==` pins fix sphinx and docutils across the whole lock. This is existing behaviour rather than anything new — `uv sync` on `master` resolves to the same thing today — but committing the lock makes it visible, and it is worth knowing before anyone is surprised by a `uv sync` dev environment on sphinx 7.4. ## Out of scope `requires-python` and the CI matrix are **untouched**; the lock is generated for the current `>=3.10,<4`. The move of the floor to 3.11 is user-visible (matrix, classifiers, changelog) and belongs in its own pull request. The tox configuration is likewise left alone. No changelog entry, matching #1804, #1805 and #1806.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
.github/workflows/prek-update.yaml, aPrek updateworkflow that keeps the hookrevisions in
.pre-commit-config.yamlcurrent and opens a pull request with the result.It replaces the weekly autoupdate PR that pre-commit.ci has been raising.
What it does
Runs monthly —
cron: "0 6 1 * *", 06:00 UTC on the 1st, the same cadence dependabotuses in this repo — and on
workflow_dispatchfor manual runs. Each run:uv run prek update --cooldown-days 7, on the sameastral-sh/setup-uv@v10.0.1 (enable-cache: true)toolchain theLintjob uses. Theseven-day cooldown keeps day-zero hook releases out: as of today it selects ruff
v0.16.4and holds backv0.16.5, which is younger than a week.no hook updates eligibleand ends green having creatednothing. No empty PRs.
prek run --all-filesover the updated tree, so formatterand
--fixchurn arrives already applied rather than landing on whoever merges. Hooksthat still report findings do not fail the workflow — the PR is opened anyway, red,
with
hooks clean after autofix: noand a link to the run. A rev bump that needs humanattention should be visible, not silently withheld.
prek-update, so a re-run updates the existing PRinstead of stacking a new one. The body lists each repository with its old and new rev.
Why a GitHub App token
A pull request created or pushed with the default
GITHUB_TOKENdoes not triggerpull_requestworkflows — GitHub's anti-recursion rule.Lintis a required check onmaster under strict mode, so such a PR would never become mergeable. The workflow
therefore mints a token from the org bot app (
BOT_APP_ID/BOT_PRIVATE_KEY, bothrepository secrets) and uses it for every write. Top-level permissions stay
contents: read; the default token only ever performs the checkout.One setup item to confirm: the app needs
ContentsandPull requestsread/write, andideally
Workflows: Read & writeas well.yamlfmtis one of the hooks being bumped andit formats
.github/workflows/*.yaml, so a future yamlfmt release that changes itsformatting would put a workflow file in the PR, and GitHub rejects app pushes that touch
workflows without that permission. Runs fail loudly if it is missing, so it is safe to
add later.
Why not keep pre-commit.ci
prek updateis workspace-aware: one invocation updates every.pre-commit-config.yamlin the tree. pre-commit.ci reads only the root config with vanilla pre-commit, so it
cannot follow the per-package configs the uv-workspace monorepo layout will introduce.
This workflow is monorepo-ready; pre-commit.ci is not.
Merge ordering
Merge this after #1805. #1805 carries the same ruff and mypy rev bumps together with
the
selectpin that makes them tolerable. If this lands first, its next run re-proposesthose bumps without that pin — a rehearsal on current master produced 151 remaining
ruff findings and a mypy
unused-ignore, across 23 rewritten files.After merge
gh workflow run prek-update.yaml. With 🔧 Unify the ruff and mypy pins, and pin the rule set that goes with them #1805 in, expect theno-op path —
no hook updates eligible, green, no PR. That single run also proves theapp token and its install, which cannot be checked any other way. The first real PR
appears once a hook release clears the seven-day cooldown.
both superseded — autofix by the
Lintgate, autoupdate by this workflow. Nothing inthis PR touches
.pre-commit-config.yaml, so theci:block question does not arise.