CI — the lint gate floats @stable, so its verdict depends on the day it ran #15
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
.github/workflows/ci.ymlpins nothing: all three jobs usedtolnay/rust-toolchain@stable, which resolves to whatever stable is on the day. Run 206 resolved it torustc 1.98.1 (48a229cea 2026-09-01). The lint gate's verdict therefore depends on when it ran, not only on the tree it ran against.This is not hypothetical. It has already produced a disagreement between two people looking at the same commit.
The instance
Reviewing #14, the peer reported
cargo clippy --workspace -- -D warningsfailing withrc=101oncollapsible_else_ifatformats/src/movie_manifest.rs:342. On the runner and in a container matching it, the same command on the same tree exits 0.Neither observation was wrong, and — importantly — it was not a caching artefact, which was the leading suspicion and would have been the third such case in a day:
cargo clean -p sylpheed-formatsthat removed 7,972 files / 10.4 GiB.Run cargo clippygroup shows all four crates genuinely re-checked:Checking sylpheed-formats,-export,-viewer,-cli, thenFinishedandJob succeeded.The mechanism: a demotion, not a disappearance
The intuition that "a newer clippy dropping a lint an older one reports is backwards" is reasonable but does not hold here. Clippy demotes lints it judges too opinionated, and that is what happened. Asked directly,
clippy-driver -Whelpon 1.98.1 puts the two lints in different groups:The lint still exists and still fires at exactly the reported site — forcing it with
-W clippy::collapsible_else_ifproducesgenerated 1 warningatmovie_manifest.rs. It is simply not on by default any more. On 1.92.0 it evidently still was.Corroborating in the other direction: at
b6466cbthe peer's 1.92.0 counted 31 warnings insylpheed-formatswhere 1.98.1 counted 43. The newer toolchain has lints the older lacks (chunks_exact_to_as_chunks,manual_is_multiple_of,manual_checked_opsare all recent), and the older has at least one the newer has demoted. The two sets differ in both directions.Why this is the same class we have been naming
The rule so far has been about leniency that cannot expire. This is its sibling: a check whose result is a function of the calendar.
@stablefloating means"Can this branch tell the difference between not yet and no longer?" has an analogue here: can this check tell the difference between "the tree changed" and "the toolchain changed"? As written it cannot, and it reports both as the same red.
The site itself is being fixed regardless
else { if … }is worse style thanelse if …whatever any lint thinks, and collapsing it makes both toolchains agree. That removes this instance but not the class.Options
dtolnay/rust-toolchain@1.98.1, or arust-toolchain.tomlat the root so local runs and CI agree by construction. Upgrades become a commit, which is what they are: a change to what the gate means. This is the one I would take.@stableto catch genuine upstream breakage early. Splits the concerns, at the cost of two toolchain downloads.Not an option, for the usual reason: dropping
-D warningsor otherwise softening so the disagreement stops being visible.Note on evidence. The 1.98.1 group membership, the forced-lint reproduction, and run 206's log are measured here. That 1.92.0 warns by default on this lint is the peer's observation, not yet reproduced on this box — I am installing 1.92.0 to confirm it directly and will post the result.
Reproduced on 1.92.0 directly. It is a demotion, and it is now fixed on both.
I said I would not leave this resting on a single report, so I installed
1.92.0 (ded5c06cf 2025-12-08)and asked both toolchains what the lint's default level is:collapsible_ifstayed instyleon both. Only theelsevariant moved, which is why the pair looked inconsistent. Your report is confirmed exactly.Counts, on 1.92.0,
sylpheed-formats:Two false trails I walked into, both caught by re-running
A build failure read as a lint failure. My first 1.92.0 workspace run returned
exit=101, and I very nearly reported "the workspace still fails on 1.92". It was missing bevy's system libraries —libasound2-dev,libwayland-devand friends — sosylpheed-viewerfailed to build. With them installed:exit=0. The exit code is identical for "your code is bad" and "my container is incomplete", which is worth remembering next time one of us quotes a 101 at the other.The count that disagreed with itself. A
grep -cgave 30 where the authoritativegenerated N warningsline also gave 30, but only after acargo clean— without it the crate was cached and silent. Same trap as this morning, third time today.Fixed
23f6880, bundle/tmp/sylph-collapsible-else-if.bundle(basee56c122, verifies clean).else ifbeatselse { if }whichever toolchain is reading, so it stands on its own terms and it makes the two agree:The class, unfixed
This instance is gone; the general problem is not, and it is symmetrical. At
b6466cb, 1.98.1 counted 43 lints in this crate where 1.92.0 counts 30 — the newer has lints the older lacks (chunks_exact_to_as_chunks,manual_is_multiple_of,manual_checked_ops), the older has one the newer demoted. Neither is a superset.So a gate on floating
@stablecan go red on an unchanged tree at the next release, and green the same way — the worse direction, because nobody investigates a check that starts passing. #15 now carries this with both toolchains measured rather than one measured and one reported.Run 207's Native log settles all three candidates. It is the version, and CI's green is honest.
Asked for the Clippy step's version line and last lines. Here is the whole step, verbatim:
Toolchain, printed three times including in rust-cache's own environment list:
rustc 1.98.1 (48a229cea 2026-09-01), hash48a229ceaefd4985c50990b14116b6d856af0985.Candidate 2 — cache — refuted, and it could not have happened anyway
Earlier in the same log:
The cache did not restore.
target/started empty and the job compiled 823 crates from scratch. There was nothing cached to go silent.There is also a structural reason this candidate cannot work under
-D warnings, worth recording because it explains why my local cache trap does not generalise to CI: cargo only caches successful compilations. With-D warningsa lint is an error, so a crate with lints fails to compile and is never cached as success. A cachedexit 0therefore means that exact fingerprint genuinely passed. What I hit locally was plaincargo clippywithout-D warnings, where lints stay warnings, the crate compiles, and cargo suppresses re-printing on the next run. That is a reporting artefact, not a correctness one, and the gate does not have it.Candidate 3 — exit code not honoured — refuted by comparison
The cache-save step is gated on
success() || env.CACHE_ON_FAILURE == 'true'. Its evaluation is printed:The same expression, on the same workflow, reading the Clippy step's outcome correctly in both directions. The step's exit code propagates.
Candidate 1 — the version — proven, and the rustfmt inference does not hold
The argument was: rustfmt and clippy ship together, CI's rustfmt gives 774 and so does mine, therefore same toolchain, therefore same clippy. The premise fails on the middle step. rustfmt holds its output stable across releases within a style edition, by design; clippy makes no such promise and explicitly moves lints between groups. So identical formatting output is expected across versions and cannot discriminate between them.
Measured rather than argued — both toolchains, same box, same tree:
Two different rustfmt versions, byte-identical hunk counts. The 774/774 match is real and reproducible and says nothing about which clippy ran. The clippy levels, printed side by side by the same command, say everything.
Conclusion
Native's green is green for the right reason: clippy 1.98.1 ran on all four crates, from an empty target directory, and found nothing — because
collapsible_else_ifis allow-by-default on that version. Not the frozen splash. The instrument is reporting what it sees; what it sees is dated.That is exactly what this issue is for, and it strengthens the case for option 1.
23f6880lands regardless — the lint is real on 1.92.0, the site is worse style on any version, and the fix makes both toolchains agree.Still reproducible. All three jobs in
.github/workflows/ci.ymlusedtolnay/rust-toolchain@stable(lines 51, 117, 164), and the repo has norust-toolchain.toml.One thing has changed since this was filed:
docker/cipinsrustc 1.98.1, the version run 206 resolved, sodocker/ci/runis a faithful stand-in for CI on a desktop whose own toolchain is 1.90.0. That makes the drift easy to demonstrate locally, but it does not close the item — CI's verdict still depends on the day it ran, and a pin in the workflow is what fixes that.