ci(deps): split the cadence, hold two crates, and stop batching pre-1.0 npm - #490
Merged
Conversation
….0 npm Follow-up to #484, which is now doing its job -- the recreated cargo batch (#489) came back with 7 minor/patch crates instead of 16 with majors inside. Three things that first pass did not cover, and one that was planned and is not here. **Cadence.** npm and cargo move to `quarterly`; github-actions stays `monthly`. Security advisories never use this schedule -- they are a repository setting and open their own pull requests as they land -- so version updates exist only to stop drift, and drift costs what somebody else's deadline costs. On Actions somebody else always sets it: runner images retire toolchains on their own timetable, which is how `build.yml` came to build releases on a Node pulled from the image toolcache (#485). An Actions bump is also usually a one-line tag move. Nothing external forces a TypeScript or a serde upgrade, and a quarter of them in one grouped pull request is less interrupting and more reviewable than three monthly ones. **`minor` does not mean compatible below 1.0, and the two ecosystems disagree about it.** Dependabot types an npm update from the literal position of the number, so katex 0.16.47 -> 0.18.1 is a `minor` and went straight into #487 -- the batch whose whole premise is that it is safe to skim. It is not: KaTeX 0.18.0 renamed every internal CSS class and 0.17.0 changed `__defineFunction`, both flagged BREAKING upstream. npm's own caret rule agrees, since `^0.16.47` stops before 0.17. Cargo does not have this bug. `Dependabot::Cargo::Version` implements the pre-1.0 rule, so windows 0.61.3 -> 0.62.2 counts as a major -- which is visible in #489, where those crates are absent from the batch. The npm side has no equivalent subclass. There is no `update-types` value for "pre-1.0 minor", and `patterns` match names rather than versions, so this cannot be written as a rule. It can be written as a list: `exclude-patterns` names the three pre-1.0 npm dependencies, and they get individual pull requests. The test derives that list from package.json, so a new 0.x dependency -- or an existing one reaching 1.0 -- fails the suite instead of quietly rejoining the batch. **Two crates held.** `tauri` requires `windows ^0.61` and `webview2-com ^0.38`, still true at 2.11.5. Taking `windows` 0.62 or `webview2-com` 0.39 puts two `windows-core` versions in one graph and the build stops in our own WebView2 call with E0599. Grouping already keeps them out of the batch; what is left is an individual pull request every quarter that cannot be merged at any published tauri 2.x. Bounded to one minor series each rather than written as a semver level, so the entries lapse. The semver-level form would work -- `Cargo::Version` overrides `ignored_major_versions` so ">= 0.62" is what major means for a 0.61 crate -- but it would also swallow 0.63 and 0.64 in silence. `windows` 0.63 does not exist yet; the day it does, Dependabot proposes it and we find out whether tauri has moved. The Tauri packages themselves are deliberately not ignored, and the file says so: the lockfile is on 2.10.2 while 2.11.5 shipped 2026-07-01, and the red pull request is the only notification there is. **The cooldown is not here, because the case for it does not survive being checked.** The mechanism is fine -- `cooldown` is valid alongside `groups`, dependabot-core passes it on both the grouped and the individual path, and it cannot touch a security update (`update_cooldown: job.security_updates_only? ? nil : job.cooldown` in both). What fails is the premise that the red pull requests are red because their targets are fresh: - typescript 7.0.2 shipped 2026-07-08; svelte-check 4.7.4 shipped 2026-07-27, nineteen days later, and narrowed its peer range to `^5.0.0 || ^6.0.0` -- excluding 7 on purpose. - windows 0.62.2 has been out since 2025-10-06 and webview2-com 0.39.1 since 2026-03-11. Ten months and five. - katex 0.18.1 is three weeks old, but it is breaking at any age. - the rest is the Tauri parity guard, which is not about age at all. None of them is a freshness problem. And on a quarterly schedule a major caught by an N-day window is not delayed N days, it is delayed a quarter. Also worth recording: GitHub Actions does not support `semver-major-days` at all, so a cooldown could never have applied to the one ecosystem that stays monthly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 6, 2026
Closed
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.
Tuning for the bot #484 just landed. That change is working — the recreated cargo batch (#489) came back with 7 minor/patch crates instead of 16 with majors inside, and
tauri-plugin-prevent-default 2→5/notify 6→8are gone from it. This is the next layer: when to run, what "minor" is worth below 1.0, and two crates that cannot be merged at all.1. Cadence split
npmandcargo→quarterly;github-actionsstaysmonthly.Security advisories never use this schedule, so version updates exist only to stop drift — and drift costs what someone else's deadline costs. On Actions somebody else always sets it: runner images retire toolchains on their own timetable, which is exactly how
build.ymlcame to build releases on a Node pulled from the toolcache (#485). An Actions bump is also usually a one-line tag move. Nothing external forces a TypeScript or serde upgrade.quarterlyverified as a documentedschedule.intervalvalue — "run on the first day of each quarter (January, April, July, and October)". Available on github.com; GHES needs ≥ 3.19.2.
minordoes not mean compatible below 1.0 — and npm and cargo disagree about itThis is the one worth reading.
Verified in source.
semver_rules_allow_grouping?comparessemver_segments, so for npmkatex 0.16.47 → 0.18.1hasmajor 0 == 0, thenminor 18 > 16→ returnsupdate_types.include?("minor")→ true, it goes in the batch. npm's own caret rule says the opposite:^0.16.47stops before0.17, because below 1.0 the minor is the breaking position.Verified in the wild. #487 — the batch whose premise is that it can be skimmed — contains:
0.16.470.18.10.55.10.56.0and it rewrites the manifest
^0.16.27→^0.18.1. KaTeX's own release notes:BREAKING CHANGES: users who apply custom styles or have allowlists targeting KaTeX's internal classes must update their selectors.(CSS class prefixing, ~20 classes)BREAKING CHANGES: The internal API for __defineFunction changed.Two breaking releases inside a
chore(deps)batch labelled minor/patch.Cargo does not have this bug.
Dependabot::Cargo::Versionoverrides the pre-1.0 rule, sowindows 0.61.3 → 0.62.2counts as a major, falls out of the group — and you can see it in #489, wherewindowsandwebview2-comare simply absent from the batch.NpmAndYarn::Versionhas no equivalent override; I checked, it definesmajor/minor/patchand nothing else.The fix, and why it is a list. There is no
update-typesvalue for "pre-1.0 minor", andpatternsmatch on name, not version (WildcardMatcher.match?(rule, dependency_name)), so this cannot be expressed as a rule. Soexclude-patternsnames them:exclude-patternsis the mechanism with a documented outcome — Example 2 in GitHub's grouping guide: excluded dependencies are ones "Dependabot continues to raise single pull requests for". I considered leaning on thePatternSpecificityCalculatorinstead (exact match scores 1000,*scores 1) but rejected it: it's an implementation detail I'd only have read onmain, whereasexclude-patternsis checked directly inDependencyGroup#contains?.A hand-written list rots, so the test derives the expected list from
package.jsonand asserts set equality. Adding a 0.x dependency, or an existing one reaching 1.0, fails the suite.3. Two crates held
taurirequireswindows ^0.61andwebview2-com ^0.38— still true at 2.11.5. Taking 0.62 / 0.39 collides twowindows-coreversions and fails atsrc-tauri/src/lib.rs:1966withE0599. Grouping already keeps them out of the batch (see above), so what remains is an individual PR every quarter that cannot be merged at any published tauri 2.x.Bounded series rather than a semver level, so the entries lapse:
windows0.63 does not exist yet, and the day it does Dependabot proposes it and we learn whether tauri has moved. A semver-level ignore would swallow 0.63 and 0.64 silently.The Tauri packages are not ignored, and the file now says why: the lockfile is on tauri 2.10.2 while 2.11.5 shipped 2026-07-01, and the red PR is the only notification that exists.
4. The cooldown is not in this PR
The mechanism checks out —
cooldownis valid alongsidegroups; dependabot-core passesjob.cooldownon the grouped path (group_update_creation.rb:404) and the individual path (update_all_versions.rb:249), so it would reach the individual major PRs; and it cannot delay a security update, both byupdate_cooldown: job.security_updates_only? ? nil : job.cooldownon both paths and by the docs ("only available for version updates, not security updates").The premise is what fails. Nothing currently red is red because its target is fresh:
^5.0.0 || ^6.0.0— deliberatewindows0.62.2^0.61webview2-com0.39.1^0.38And a cooldown now costs more than it did: on a quarterly schedule a major caught by an N-day window is not delayed N days, it is delayed a quarter. Also: GitHub Actions does not support
semver-major-days(docs table —default-daysonly), so a cooldown could never have applied to the one ecosystem staying monthly.Say the word and I'll add
semver-major-days: 7to npm and cargo, but I'd be shipping something I can't show a benefit for.Falsification
Per-ecosystem on the cadence test, as asked — each mutation names the block it broke.
monthlythe header comment promises npm runs quarterly, but its block says monthlymonthlythe header comment promises cargo runs quarterly, but its block says monthlyquarterlythe header comment promises github-actions runs monthly, but its block says quarterlymonthlythe header comment promises npm runs monthly, but its block says quarterlythe header comment must state a cadence for every ecosystem and no otherskatexdropped from exclude-patternsthe npm group's exclude-patterns must name exactly the pre-1.0 dependencies in package.jsonthe npm group must exclude its pre-1.0 dependencies (katex, monaco-editor, monaco-vim); without that they ride along in the grouped pull request as ordinary minor bumpsmermaid(a 11.x dep) added to exclude-patternsthe npm group's exclude-patterns must name exactly the pre-1.0 dependencies in package.json^0.3.1dependency added to package.jsonversions:every ignore entry under cargo must carry a versions: list; an entry without one silences the dependency permanently, including its security updates'>= 0.62'ignoring windows at '>= 0.62' does not name a bounded version series, so nothing will ever reopen the question'0.*'ignoring windows at '0.*' does not name a bounded version seriesignore:cargo has an \ignore:` key this test cannot parse, so it cannot vouch for it`The four properties from #484 were re-falsified after the rewrite and still fail correctly. Re-indenting
- package-ecosystem:turns all seven red rather than passing vacuously.npm run check— 0 errors, 650 files.npm test— 756/756.What I could not verify
quarterlyand theignoreversion syntax are unvalidated against Dependabot's own parser. There is no public validator.0.62.*follows the documented "standard pattern for the package manager" and Cargo accepts it as a requirement; if Dependabot rejects it the config fails loudly on the next run rather than silently.src/lib/utils/exportFonts.tsis the one place that reads KaTeX class names, and it is deliberately written to read them out of the shipped stylesheet at runtime — so a consistent rename is absorbed. The argument for excluding it is that a documented breaking change should not arrive inside a batch labelled minor/patch, not that I proved this one breaks us.dependabot-corereading is frommain, not the revision GitHub's hosted Dependabot runs.exclude-patternsmeans 0.x patch bumps also get individual PRs. Three dependencies on a quarterly cadence, so a handful of extra PRs a year. That is the cost and I have not tried to avoid it.🤖 Generated with Claude Code