Repository navigation
🔧 Dependabot: back to the default strategy, with the review rule written down - #1944
Conversation
…ten down #1942 set `versioning-strategy: increase-if-necessary` for the uv ecosystem to stop dependabot rewriting manifest specifiers it does not need to. Measured after the merge: under that strategy EVERY dependabot job for this repository errors -- "Dependabot can't resolve your Python dependency files" -- because every strategy except lockfile-only unlocks a requirement before asking uv for the newest resolvable version, and for the `typing` group's `docutils~=0.20.0` that asks uv to resolve docutils 0.23 beside `sphinx~=7.4`, which forbids it. Under the default the same probe degrades to "no update"; under `increase-if-necessary` it is fatal to the whole job. Both refreshes of #1940 after the merge failed that way, and the run triggered by the config change proposed nothing. `lockfile-only` would avoid the error but never proposes a release outside a range, and 44 of the 97 external specifiers here carry a cap or a series. So the option goes, and what stays is the review: the comment in `.github/dependabot.yml` records both measurements, and the root `AGENTS.md` gains one paragraph telling a reviewer of a dependabot uv pull request to read the `pyproject.toml` hunks, restore the specifier lines by hand, relock, and give a cap or series that really should move its own pull request.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1944 +/- ##
=======================================
Coverage 91.69% 91.70%
=======================================
Files 129 129
Lines 18155 18152 -3
=======================================
- Hits 16648 16647 -1
+ Misses 1507 1505 -2
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:
|
|
Correction, retracting one claim in this pull request's description and in the comment it added to |
… fails every uv job Every rebase or recreate of #1940, and the run after #1944 reverted the versioning strategy, errored with "Dependabot can't resolve your Python dependency files". #1944 blamed the strategy; that was wrong, and this commit says so where the claim was made. The failing probe is docutils: since sphinx-codelinks arrived (#1871) with a bare `docutils` dependency, docutils is a direct, unpinned dependency, so dependabot tries to lock 0.23 -- by writing `docutils==0.23` on every requirement line it finds, the `typing` group's `docutils~=0.20.0` included -- and uv refuses that beside `sphinx~=7.4`. The lock already carries docutils at 0.20.1 in the typing split and 0.21.2 elsewhere, which is what `[tool.uv] conflicts` is for; dependabot has no way to express that, and the uv updater treats the refusal as fatal to the whole job. So docutils joins types-docutils under the same ignore: major and minor updates ignored, patches inside the series still flow, and the default environment's docutils moves by hand with `uv lock --upgrade-package docutils`. The earlier rationale for not listing it -- keeping new series flowing to the main environment -- never operated, because docutils was not a direct dependency until #1871; the default environment sat on 0.21.2 with 0.23 out. `increase-if-necessary` is therefore untested rather than unusable, and the comment records it as a candidate to retry once this rule is seen to keep the jobs green; the AGENTS.md paragraph is corrected to match.
… fails every uv job (#1946) Adds `docutils` to the uv ecosystem's ignore list for major and minor updates, beside `types-docutils`, and corrects a wrong claim #1944 made. ### The error, and what it is not Every rebase or recreate of #1940 errored with "Dependabot can't resolve your Python dependency files", and so did the rebase job that ran after #1944 reverted `versioning-strategy`, under the default: ``` Because sphinx>=7.4.0,<=7.4.7 depends on docutils>=0.20,<0.22 and sphinx-needs-workspace:typing depends on docutils==0.23, … ``` #1944 attributed the error to `increase-if-necessary`. That was wrong: the same job fails under the default strategy. The comment in `dependabot.yml` and the paragraph in `AGENTS.md` now say so where the claim was made. ### What it is docutils 0.23 has been out since May. What changed is #1871: sphinx-codelinks arrived with a bare `docutils` dependency, so docutils became a *direct, unpinned* dependency in dependabot's eyes, and dependabot now tries to lock 0.23. It does that by writing `docutils==0.23` on every requirement line it finds, including the `typing` group's `docutils~=0.20.0`, and uv correctly refuses that beside `sphinx~=7.4`, which needs docutils below 0.22. The lock already carries docutils at 0.20.1 in the typing split and 0.21.2 everywhere else, which is exactly what `[tool.uv] conflicts` provides; dependabot has no way to express "0.20.x here, newest elsewhere", and the uv updater treats uv's refusal as fatal to the whole job. Group refreshes before #1871 (the 5 September batch, #1851) rebased fine. ### The fix docutils joins `types-docutils` under the same ignore: major and minor updates ignored, patches inside the series still flow. The earlier rationale for leaving docutils unlisted, keeping new series flowing to the main environment, never operated: dependabot only moves a lock entry for a direct dependency, and docutils was not one until #1871, so the default environment sat on 0.21.2 with 0.23 out. It moves by hand now, with `uv lock --upgrade-package docutils`, the way the typing series itself does. ### Consequences for the strategy question `increase-if-necessary` (#1942) was reverted for a reason that turns out not to hold, so it is untested rather than unusable. The comment records it as a candidate to retry once this rule has been seen to keep the jobs green; that is a separate decision and a separate pull request. ### Proof after the merge The dependabot run this config change triggers should complete green on the Insights page, where the red "Rebase update" entries are. There is no open dependabot PR to refresh right now, so that run is the evidence. ### Checked `uv run poe lint` 8/8. No lock change.
…pgrade` job (#1951) ### Why Dependabot's uv updater cannot update this lock, for two measured reasons. **It pins one candidate version on every requirement line before it relocks.** This workspace legitimately locks some packages at two versions across its `[tool.uv] conflicts` splits — `docutils` 0.20.1 in the `typing` group beside the newest elsewhere, `myst-parser` 4.x in the `sphinx-7` cell beside 5.x elsewhere. uv refuses the universal pin, and the updater treats the refusal as fatal to the whole job: every group refresh died with "Dependabot can't resolve your Python dependency files". **Its default `versioning-strategy` rewrites manifest specifiers it has no reason to touch.** #1940 raised `matplotlib>=3.3.0` to `>=3.11.2` on a user-facing extra whose floor already allowed the release, and widened `click < 8.2` past the comment explaining the cap. Three merged pull requests (#1942, #1944, #1946) and a fourth (#1948, six more ignore rules, closed unmerged in favour of this) went into working around that. Meanwhile `uv lock --upgrade` does the job in one command: on master today it resolves cleanly across every split and moves 13 packages, including `docutils v0.20.1, v0.21.2 -> v0.20.1, v0.22.4` — the exact thing dependabot cannot express — and it never touches a manifest. ### What this does Adds `.github/workflows/uv-update.yaml` — `UV update`, monthly on `0 7 1 * *` (an hour after `Prek update`, so both land on the same morning and neither depends on the other) plus `workflow_dispatch` — modelled step for step on `prek-update.yaml`: the bot-app token, `actions/checkout@v7`, `astral-sh/setup-uv@v10.1.0` on the root `.python-version`, and one pull request from the fixed branch `uv-update` titled `⬆️ Update the uv lock (monthly)`. The steps: `uv lock --upgrade`, capturing uv's lock-diff lines (stderr, so `2>&1` into `$RUNNER_TEMP`); an assertion that **no manifest changed**, which fails the job loudly if one did; `uv sync --frozen` and `uv run --frozen prek run --all-files`, recording whether the hooks came out clean; the held-back list; and the composed body. The body reports **all three of uv's lock-diff verbs**, not just `Updated`. `Added` and `Removed` get bullets of their own — a package entering the dependency graph is the most review-worthy line a lock update can carry, and a body that drops it silently is worse than one that says nothing — and any other `<Verb> <name> v<version>` line uv may invent is printed as `unrecognised uv line:` rather than discarded, so the body announces a new verb instead of quietly shrinking. Measured on a real 18-line upgrade log: 18 lines in, 18 bullets out. Then `.github/dependabot.yml` loses its uv section — ignore rules, `ty` group and all — and gains ten lines saying why. `AGENTS.md`'s dependabot-review paragraph is replaced by one paragraph on the job, the same length as the one it replaces, with the history left in `dependabot.yml` where it belongs. `tools/tests/test_dependabot_config.py` fences the configuration so the uv entry cannot come back, and fences the replacement *as a command* in the `update` step — `uv lock --upgrade` also appears in prose in the same file, so a substring check would stay green while the job was gutted to a no-op relock. ### What stays with dependabot The **`github-actions`** ecosystem, untouched: that half has never had a problem here. And **security alerts and security-update pull requests**, which are a repository feature reading `uv.lock` independently of `dependabot.yml`'s version-update config — removing the uv section does not stop them. ### Two decisions, with their measurements **No cooldown**, unlike `prek update --cooldown-days 7`. `uv lock --upgrade --exclude-newer` is a *global index cutoff*, not a per-package delay, and it failed both halves of the test: at seven days it made the lock unresolvable (`Because only ty<=0.0.79 is available and sphinx-needs-workspace:typing depends on ty>=0.0.80,<0.1.dev0 …` — the `typing` group's own floor is younger than the window, and so would be any floor a release bump had just raised); and with a cutoff recent enough to resolve, it wrote `[options] exclude-newer` into `uv.lock`, after which a plain `uv lock --check` reported "The lockfile at `uv.lock` needs to be updated" and the `uv-lock` prek hook rewrote the lock and failed — on the job's own pull request. The workflow carries a comment saying so. Dependabot had no cooldown here either, so nothing is lost. **The held-back list comes from `uv tree --outdated --frozen --depth 1`**, run after the upgrade. `uv pip list --outdated` was tried first and cannot do it: it reads one installed environment, so `pydata-sphinx-theme` (behind the `theme-pds` extra) is invisible to it — as is every conflicting group. `uv tree` reads the lock, `--depth 1` is each member's own requirements, and running it after the upgrade leaves only what is held back rather than what was merely pending. Scored against the known positives: it flags `jsonschema-rs` (0.53.0, capped `<0.54.0`) and `pydata-sphinx-theme` (0.17.1, capped `<0.20.0`), and correctly does *not* flag `comment-parser` (already newest wherever installable) or `myst-parser` (already 5.1.0 by default; 4.0.1 survives only in the `sphinx-7` and `typing` cells, held by `sphinx~=7.4`). A grep over the manifests puts the specifier beside the versions — and the body says plainly what that column is: every specifier this workspace writes for that name, while what actually blocks the newer release may be one of those, a cap elsewhere in the graph, or a `[tool.uv] conflicts` split, with `uv lock --upgrade-package <name>` as the way to find out. The list is the **default resolution's** view; a package held back only in a matrix split is not in it, and the body says that too. ### Proving it without merging it A `workflow_dispatch` needs the file on the default branch, so the five `run:` blocks were extracted from the YAML *verbatim* (parsed with pyyaml, the way `tools/tests/test_ci_plantuml_steps.py` reads workflow YAML) and run locally under `bash -e` with the runner's environment variables. Three runs: * **on today's lock** — `changed=true`, the 13 `Updated` lines captured, manifests untouched, all eight hooks Passed against the upgraded lock (ty included), the held-back list carrying both known positives, and the body composed in full; * **with the lock already current** — `changed=false`, "no lock updates available; nothing to open a pull request for", and every later step skipped by its `if:`; * **with a floor edited by hand** (dependabot's own `matplotlib>=3.3.0` → `>=3.11.2`) — the assert step fails with `::error::uv lock --upgrade edited a manifest; this job only ever moves uv.lock` and `exit 1`. Plus, for the body's new verbs: a constructed log covering `Updated`/`Added`/`Removed`/an unknown verb, and a real 18-line upgrade log rebuilt from a two-week-old `uv.lock`. And for the fence: weakening the update step's `uv lock --upgrade` to `uv lock` turns the test red. `uv.lock` is deliberately **not** part of this pull request; the tree was returned to clean before anything was committed. ### After the merge Dispatch `UV update` once by hand as the rehearsal; it opens the first real pull request. Then delete the `ci/dependabot-ignore-unlockable` branch, kept on origin as the fallback until this landed. ### Review Built by one agent against a binding brief, reviewed by a second that re-ran every proof from its own extraction of the YAML and added 15 mutations of its own; its three findings (the dropped `Added`/`Removed` lines, the fence matching prose, the misleading "no upper bound" sentence) are the fix round folded in above, and it re-verified the two mechanisms afterwards (the invented verbs `Downgraded`/`Yanked` come out loud, five uv summary shapes stay ignored; the fence is red for a weakened command and green for weakened prose). Two constructed, not observed, residuals remain and are accepted: a version *pair* on an `Added`/`Removed` line would malform that bullet's PyPI link (the package is still named), and a hyphenated verb would slip the catch-all. Gates re-run by hand on the final head: `uv run poe lint` 8/8, `pytest tools/tests` 316 passed.
Reverts #1942's
versioning-strategy: increase-if-necessaryand records, in two places, what was measured and what a reviewer does instead.What went wrong with #1942
After the merge, every dependabot job for this repository errored with "Dependabot can't resolve your Python dependency files":
The mechanism, from dependabot-core's python update checker (which uv reuses): every strategy except
lockfile-onlyunlocks a requirement before asking uv for the newest resolvable version. For thetypinggroup'sdocutils~=0.20.0that asks uv to resolve docutils 0.23 besidesphinx~=7.4, which forbids anything above 0.21. Under the default strategy that probe degrades to "no update for docutils" (which is why the 2026-09 batch, #1940, came through with three updates and no docutils); underincrease-if-necessaryit is fatal to the whole job. Both refreshes of #1940 after the merge failed that way, and the run triggered by the config change proposed nothing. Net effect: no dependabot updates of any kind.lockfile-onlywas the other candidate. It never unlocks, so it cannot hit this, and it never edits a manifest, but it also never proposes a release outside a range. 44 of the 97 external specifiers in these manifests carry a cap or a series, and the widening pull request is the one signal that a new major of one of them exists. Not worth the silence.What this does instead
.github/dependabot.yml: the option is removed, and the comment beside the uv block records both measurements and why the default stays.AGENTS.md: one paragraph under Pull request requirements telling a reviewer of a dependabot uv pull request to read thepyproject.tomlhunks, not only the lock; what the default strategy does to floors, caps and~=series, with ⬆️ Bump the python-minor-and-patch group across 1 directory with 3 updates #1940's three cases as the examples; and the procedure: keep the lock update, restore the specifier lines by hand on the dependabot branch, relock, and give a cap or series that really should move its own pull request (⬆️ Drop sphinx-codelinks' click cap: it pinned a package nothing uses #1943 is the shape).After the merge
Dependabot's run triggered by this config change should complete green on the Insights page, which is the proof the jobs are back. #1940 then gets its specifier lines restored by hand and is merged as the lock update it should have been; #1943 drops the click cap on its own reasoning.
Checked
uv run poe lint8/8 (yamlfmt, taplo, check-readme included). No lock change.