Nothing in the repo pins the local toolchain, so local gates track a moving stable while CI pins 1.98.0 #122

Open
opened 2026-08-25 13:46:25 +00:00 by jlxq0 · 0 comments
Owner

Found while running the release gates for v0.10.5 on 21d74338. Not a defect in the tree — all five gates pass — but a gap between what CI measures and what a local run measures, which is the class of thing that turns a green local check into non-evidence without anything failing.

The asymmetry

.forgejo/workflows/ci.yml:41 pins the toolchain exactly, with a comment two lines above saying to bump it deliberately in its own change:

# Bump this deliberately, in its own change, and read AGENTS.md first.
- uses: dtolnay/rust-toolchain@6c977a6...  # v1
  with:
    toolchain: "1.98.0"

The repository pins nothing. Verified against origin/main:

$ git ls-tree -r --name-only origin/main | grep -iE 'rust-toolchain|\.tool-versions|mise\.toml|\.mise'
(none)

No rust-toolchain, no rust-toolchain.toml, no .tool-versions, no .mise.toml. The only pin anywhere is ~/.config/mise/config.toml, which says rust = "stable" — a machine-level setting, outside the repository, that resolves to whatever stable is on the day.

Why it matters, and why it is invisible right now

Local gates and CI agree today. They agree because rustup update stable was run on this machine on 2026-08-25 and stable happens to be 1.98.0 — the same version CI pins. That is a coincidence of timing, not a property of the repository.

The next stable release silently puts local gates above the CI pin. A developer then gets a green cargo clippy --all-targets --all-features --locked -- -D warnings from a newer clippy than the one that will actually gate the merge, with nothing in the repository to compare against and no error anywhere. That is the direction ci.yml's own comment warns about, and #112 is the record of it biting from the other side: this tree could not lint below 1.98.0 and nothing said so, so three releases went out with a local clippy failing on two #[allow] attributes naming lints that clippy did not have.

So the failure mode is: a green local run stops being evidence about the pinned toolchain, and nobody is told.

Fix

Add rust-toolchain.toml pinning 1.98.0, next to the CI pin, so both move in one deliberate change and a local run cannot silently diverge:

[toolchain]
channel = "1.98.0"
components = ["rustfmt", "clippy"]

Acceptance is that changing the CI pin without changing the toolchain file, or vice versa, is visible in one diff — and that a local cargo clippy run reports 1.98.0 on a machine whose stable is something else. The second half is the one that can fail: install a newer stable, confirm cargo clippy --version still reports 0.1.98 inside the repo.

Not a bug in the tree

Stated so nobody chases it: 21d74338 passes all five gates on 1.98.0 — fmt, clippy (exit 0), test (240 passed, 0 failed), audit (498 dependencies, 0 vulnerabilities, 8 pre-existing allowed warnings), and deny check bans licenses sources.

Retraction, recorded so it does not get repeated

This issue exists because of a claim that was wrong. It was first reported as "RUSTUP_TOOLCHAIN is set in the worker environment and overrides the toolchain file". Both halves are false and were checked afterwards:

  • RUSTUP_TOOLCHAIN is unset — echo "${RUSTUP_TOOLCHAIN:-<unset>}" prints <unset>.
  • There is no toolchain file to override, per the git ls-tree above.

What was actually seen was rustup show active-toolchain printing stable-aarch64-apple-darwin (overridden by environment variable RUSTUP_TOOLCHAIN). That string comes from mise's shim setting the variable for the duration of the call it wraps. It is the shim working as designed, and reading it as a stray variable inverted the diagnosis. The real gap is the missing in-repo pin, which is the opposite shape: not an override that shouldn't be there, but a pin that isn't.

Found while running the release gates for `v0.10.5` on `21d74338`. Not a defect in the tree — all five gates pass — but a gap between what CI measures and what a local run measures, which is the class of thing that turns a green local check into non-evidence without anything failing. ## The asymmetry `.forgejo/workflows/ci.yml:41` pins the toolchain exactly, with a comment two lines above saying to bump it deliberately in its own change: ```yaml # Bump this deliberately, in its own change, and read AGENTS.md first. - uses: dtolnay/rust-toolchain@6c977a6... # v1 with: toolchain: "1.98.0" ``` The repository pins nothing. Verified against `origin/main`: ``` $ git ls-tree -r --name-only origin/main | grep -iE 'rust-toolchain|\.tool-versions|mise\.toml|\.mise' (none) ``` No `rust-toolchain`, no `rust-toolchain.toml`, no `.tool-versions`, no `.mise.toml`. The only pin anywhere is `~/.config/mise/config.toml`, which says `rust = "stable"` — a machine-level setting, outside the repository, that resolves to whatever stable is on the day. ## Why it matters, and why it is invisible right now Local gates and CI agree today. They agree **because `rustup update stable` was run on this machine on 2026-08-25 and stable happens to be 1.98.0** — the same version CI pins. That is a coincidence of timing, not a property of the repository. The next stable release silently puts local gates *above* the CI pin. A developer then gets a green `cargo clippy --all-targets --all-features --locked -- -D warnings` from a newer clippy than the one that will actually gate the merge, with nothing in the repository to compare against and no error anywhere. That is the direction `ci.yml`'s own comment warns about, and #112 is the record of it biting from the other side: this tree could not lint below 1.98.0 and nothing said so, so three releases went out with a local clippy failing on two `#[allow]` attributes naming lints that clippy did not have. So the failure mode is: a green local run stops being evidence about the pinned toolchain, and nobody is told. ## Fix Add `rust-toolchain.toml` pinning `1.98.0`, next to the CI pin, so both move in one deliberate change and a local run cannot silently diverge: ```toml [toolchain] channel = "1.98.0" components = ["rustfmt", "clippy"] ``` Acceptance is that changing the CI pin without changing the toolchain file, or vice versa, is visible in one diff — and that a local `cargo clippy` run reports 1.98.0 on a machine whose `stable` is something else. The second half is the one that can fail: install a newer stable, confirm `cargo clippy --version` still reports 0.1.98 inside the repo. ## Not a bug in the tree Stated so nobody chases it: `21d74338` passes all five gates on 1.98.0 — fmt, clippy (exit 0), test (240 passed, 0 failed), audit (498 dependencies, 0 vulnerabilities, 8 pre-existing allowed warnings), and `deny check bans licenses sources`. ## Retraction, recorded so it does not get repeated This issue exists because of a claim that was wrong. It was first reported as "`RUSTUP_TOOLCHAIN` is set in the worker environment and overrides the toolchain file". Both halves are false and were checked afterwards: - `RUSTUP_TOOLCHAIN` is unset — `echo "${RUSTUP_TOOLCHAIN:-<unset>}"` prints `<unset>`. - There is no toolchain file to override, per the `git ls-tree` above. What was actually seen was `rustup show active-toolchain` printing `stable-aarch64-apple-darwin (overridden by environment variable RUSTUP_TOOLCHAIN)`. That string comes from mise's shim setting the variable for the duration of the call it wraps. It is the shim working as designed, and reading it as a stray variable inverted the diagnosis. The real gap is the missing in-repo pin, which is the opposite shape: not an override that shouldn't be there, but a pin that isn't.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jlxq0/matrix-mcp#122
No description provided.