Skip to content

Pin the Rust toolchain: devcontainer and CI build with different compilers #207

Description

@lxsaah

Problem

The Rust compiler is an undeclared dependency, and developers and CI use
different ones.

  • The devcontainer never pins: .devcontainer/Dockerfile:89
    runs rustup ... -y with no version, so the image freezes on whatever was
    stable when it was built (currently 1.91.1) and shifts silently on every
    rebuild.
  • Every workflow uses dtolnay/rust-toolchain@stable, which always tracks
    current stable (1.97.1 at time of writing).
  • No crate declares rust-version, so there is no MSRV either.

Nobody chose this ~6-version gap, and nothing surfaces it until it breaks.

Why now

It already cost us. On #201 the new wasm-browser-tests job failed five
consecutive runs on a bug that was impossible to reproduce locally:
wasm-bindgen 0.2.105 generated a duplicate JS identifier

SyntaxError: Identifier 'wasm_bindgen_..._convert__closures_____invoke______'
has already been declared

Symbol mangling depends on the compiler, so it appeared only under CI's newer
rustc. The generated glue was a parse error, so no test ever started — the
page sat at "Loading scripts...", the harness's console divs stayed empty, and
four plausible fixes (harness timeout, module size, session ordering, Chrome
sandbox) each did nothing, because none of them touch code that never parses.
Diagnosing it took a verbose chromedriver log capture in CI.

Fixed by upgrading the wasm-bindgen family, but the toolchain gap is what made
it invisible, and every other job carries the same exposure.

Proposed change

Pin forward, to current stable — not back to the devcontainer's 1.91.1.
Pinning backwards would make CI agree with the devcontainer by freezing CI on
an old compiler, which would have hidden this bug rather than caught it:
consumers build with current stable and would still have hit it.

# rust-toolchain.toml
[toolchain]
channel = "1.97.1"
components = ["rustfmt", "clippy"]
targets = ["wasm32-unknown-unknown", "thumbv7em-none-eabihf"]

The catch — this is not a one-file change

rust-toolchain.toml overrides rustup's default, but dtolnay/rust-toolchain
installs targets:/components: onto its own toolchain. Add the file
naively and CI downloads two toolchains, then builds against the pinned one
without wasm32-unknown-unknown, thumbv7em-none-eabihf, rustfmt or
clippy — failing with missing-target errors that look nothing like the cause.

So targets/components move into the file, and all 9 install sites stop
declaring their own:

workflow sites
ci.yml 5 stable + 1 nightly (miri)
docs.yml 1
release.yml 1
security.yml 2
  • The Miri job is unaffected — cargo +nightly overrides the file explicitly.
  • .devcontainer/Dockerfile should stop installing targets by hand and let
    rustup materialize them from the file.

Also in scope

Declare a workspace rust-version. Pinning the toolchain and declaring an MSRV
are different promises: the pin says what we build with, the MSRV says what
consumers need. The repo currently makes neither, while publishing to
crates.io.

Out of scope

Bumping the pin on a schedule / via Dependabot. Worth doing, but let's land the
pin first and see how it feels.

Acceptance

  • rust-toolchain.toml committed, targets + components declared there
  • All 9 install sites reworked; full CI green
  • Devcontainer builds and make check passes on the pinned toolchain
  • rustc --version identical in the devcontainer and in CI
  • Workspace rust-version declared

Follow-up from #201 (design 049-pr201-review-closeout.md, work item A).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions