Skip to content

ci(deps): split the cadence, hold two crates, and stop batching pre-1.0 npm - #490

Merged
PathGao merged 1 commit into
masterfrom
ci/dependabot-tuning
Aug 6, 2026
Merged

ci(deps): split the cadence, hold two crates, and stop batching pre-1.0 npm#490
PathGao merged 1 commit into
masterfrom
ci/dependabot-tuning

Conversation

@PathGao

@PathGao PathGao commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

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→8 are 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

npm and cargoquarterly; github-actions stays monthly.

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.yml came 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.

quarterly verified as a documented schedule.interval value — "run on the first day of each quarter (January, April, July, and October)". Available on github.com; GHES needs ≥ 3.19.

2. minor does not mean compatible below 1.0 — and npm and cargo disagree about it

This is the one worth reading.

Verified in source. semver_rules_allow_grouping? compares semver_segments, so for npm katex 0.16.47 → 0.18.1 has major 0 == 0, then minor 18 > 16 → returns update_types.include?("minor")true, it goes in the batch. npm's own caret rule says the opposite: ^0.16.47 stops before 0.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:

package from to
katex 0.16.47 0.18.1
monaco-editor 0.55.1 0.56.0

and it rewrites the manifest ^0.16.27^0.18.1. KaTeX's own release notes:

  • 0.18.0BREAKING CHANGES: users who apply custom styles or have allowlists targeting KaTeX's internal classes must update their selectors. (CSS class prefixing, ~20 classes)
  • 0.17.0BREAKING 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::Version overrides the pre-1.0 rule, so windows 0.61.3 → 0.62.2 counts as a major, falls out of the group — and you can see it in #489, where windows and webview2-com are simply absent from the batch. NpmAndYarn::Version has no equivalent override; I checked, it defines major/minor/patch and nothing else.

The fix, and why it is a list. There is no update-types value for "pre-1.0 minor", and patterns match on name, not version (WildcardMatcher.match?(rule, dependency_name)), so this cannot be expressed as a rule. So exclude-patterns names them:

exclude-patterns:
  - 'katex'
  - 'monaco-editor'
  - 'monaco-vim'

exclude-patterns is 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 the PatternSpecificityCalculator instead (exact match scores 1000, * scores 1) but rejected it: it's an implementation detail I'd only have read on main, whereas exclude-patterns is checked directly in DependencyGroup#contains?.

A hand-written list rots, so the test derives the expected list from package.json and asserts set equality. Adding a 0.x dependency, or an existing one reaching 1.0, fails the suite.

3. Two crates held

tauri requires windows ^0.61 and webview2-com ^0.38 — still true at 2.11.5. Taking 0.62 / 0.39 collides two windows-core versions and fails at src-tauri/src/lib.rs:1966 with E0599. 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.

ignore:
  - dependency-name: windows
    versions: ['0.62.*']
  - dependency-name: webview2-com
    versions: ['0.39.*']

Bounded series rather than a semver level, so the entries lapse: windows 0.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.

Correction to something I wrote in #484. That PR's description claimed version-update:semver-major would be a no-op on these crates because Dependabot reads 0.61→0.62 as minor. That is true of the generic Dependabot::Version, which is what I read — but Cargo::Version overrides ignored_major_versions, and for a 0.61 crate "major" means >= 0.62. So the semver form would have worked; I rejected it for the wrong reason. The bounded range is still the better choice, for the lapse property, but the justification in the merged comment was wrong and is corrected in this diff.

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 — cooldown is valid alongside groups; dependabot-core passes job.cooldown on 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 by update_cooldown: job.security_updates_only? ? nil : job.cooldown on 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:

red item cause age of target
typescript 7.0.2 svelte-check 4.7.4 (2026‑07‑27, 19 days after TS 7.0.2) narrowed its peer range to ^5.0.0 || ^6.0.0 — deliberate 29 days
windows 0.62.2 tauri requires ^0.61 10 months
webview2-com 0.39.1 tauri requires ^0.38 5 months
katex 0.18.1 breaking at any age 3 weeks
tauri plugin pair cross-ecosystem parity guard n/a

And 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-days only), so a cooldown could never have applied to the one ecosystem staying monthly.

Say the word and I'll add semver-major-days: 7 to 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.

broken failing message
npm block → monthly the header comment promises npm runs quarterly, but its block says monthly
cargo block → monthly the header comment promises cargo runs quarterly, but its block says monthly
actions block → quarterly the header comment promises github-actions runs monthly, but its block says quarterly
header row for npm → monthly the header comment promises npm runs monthly, but its block says quarterly
header row for actions deleted the header comment must state a cadence for every ecosystem and no others
katex dropped from exclude-patterns the npm group's exclude-patterns must name exactly the pre-1.0 dependencies in package.json
all exclude-patterns dropped the 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 bumps
mermaid (a 11.x dep) added to exclude-patterns the npm group's exclude-patterns must name exactly the pre-1.0 dependencies in package.json
a new ^0.3.1 dependency added to package.json same — the drift direction that matters
ignore entry loses versions: every ignore entry under cargo must carry a versions: list; an entry without one silences the dependency permanently, including its security updates
ignore range → '>= 0.62' ignoring windows at '>= 0.62' does not name a bounded version series, so nothing will ever reopen the question
ignore range → '0.*' ignoring windows at '0.*' does not name a bounded version series
blank line inside ignore: 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

  • I have not run Dependabot. This is a bot config; the proof is the next run. What I can point at is chore(deps): bump the cargo group across 1 directory with 7 updates #489 vs chore(deps): bump the cargo group in /src-tauri with 17 updates #478, which is ci(deps): keep major bumps out of the grouped pull request, and split the cadence #484's effect observed rather than predicted.
  • quarterly and the ignore version 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.
  • I could not demonstrate a concrete Markpad breakage from KaTeX 0.18. src/lib/utils/exportFonts.ts is 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.
  • The dependabot-core reading is from main, not the revision GitHub's hosted Dependabot runs.
  • exclude-patterns means 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

….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>
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