From 9785a89888565637634aad002c6bab86f44d8be3 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 29 Oct 2026 11:35:37 +1100 Subject: [PATCH 1/3] build: pin Rust through Hermit rustup and rust-toolchain.toml Hermit's `rust` package is the per-host release tarball: it carries only the host's standard library and cannot add targets. Pin Hermit's `rustup` instead and move the Rust version into `rust-toolchain.toml`, matching block/buzz, so every machine and CI job resolves the same toolchain through rustup and extra targets can be added with `rustup target add`. The Windows jobs read the version from rust-toolchain.toml instead of the Hermit pin. Co-authored-by: Jitter <2b2e6415e748c35180847a37aadae06a11e30dddc76b784e7fd9354c4eb42e7a@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 Signed-off-by: Matt Toohey --- .github/workflows/ci.yml | 5 ++--- .github/workflows/release.yml | 5 ++++- bin/{.rust-1.98.1.pkg => .rustup-1.29.2.pkg} | 0 bin/cargo | 2 +- bin/cargo-clippy | 2 +- bin/cargo-fmt | 2 +- bin/cargo-miri | 1 + bin/clippy-driver | 2 +- bin/rls | 1 + bin/rust-analyzer | 2 +- bin/rust-gdb | 2 +- bin/rust-gdbgui | 2 +- bin/rust-lldb | 2 +- bin/rustc | 2 +- bin/rustdoc | 2 +- bin/rustfmt | 2 +- bin/rustup | 1 + docs/contributing.md | 7 ++++--- rust-toolchain.toml | 4 ++++ 19 files changed, 28 insertions(+), 18 deletions(-) rename bin/{.rust-1.98.1.pkg => .rustup-1.29.2.pkg} (100%) create mode 120000 bin/cargo-miri create mode 120000 bin/rls create mode 120000 bin/rustup create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 140b0bc655..9d30554189 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,9 +101,8 @@ jobs: - name: Select pinned Rust shell: pwsh run: | - $pins = @(Get-ChildItem bin/.rust-*.pkg) - if ($pins.Count -ne 1) { throw "Expected exactly one repository Rust pin" } - $version = $pins[0].Name -replace '^\.rust-(.*)\.pkg$', '$1' + $version = (Select-String -Path rust-toolchain.toml -Pattern '^channel = "(.+)"$').Matches.Groups[1].Value + if (!$version) { throw "Expected a rust-toolchain.toml channel" } rustup toolchain install $version --profile minimal --component clippy if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } "RUSTUP_TOOLCHAIN=$version" >> $env:GITHUB_ENV diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 88b204f995..b8e9aa14ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -305,12 +305,15 @@ jobs: id: pins shell: pwsh run: | - foreach ($tool in @('rust', 'node', 'pnpm')) { + foreach ($tool in @('node', 'pnpm')) { $pins = @(Get-ChildItem "bin/.$tool-*.pkg") if ($pins.Count -ne 1) { throw "Expected one $tool pin" } $version = $pins[0].Name -replace "^\.$tool-(.*)\.pkg$", '$1' "$tool=$version" >> $env:GITHUB_OUTPUT } + $rust = (Select-String -Path rust-toolchain.toml -Pattern '^channel = "(.+)"$').Matches.Groups[1].Value + if (!$rust) { throw "Expected a rust-toolchain.toml channel" } + "rust=$rust" >> $env:GITHUB_OUTPUT - name: Select pinned Rust shell: pwsh env: diff --git a/bin/.rust-1.98.1.pkg b/bin/.rustup-1.29.2.pkg similarity index 100% rename from bin/.rust-1.98.1.pkg rename to bin/.rustup-1.29.2.pkg diff --git a/bin/cargo b/bin/cargo index 6219148844..f4fc0d11c0 120000 --- a/bin/cargo +++ b/bin/cargo @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/cargo-clippy b/bin/cargo-clippy index 6219148844..f4fc0d11c0 120000 --- a/bin/cargo-clippy +++ b/bin/cargo-clippy @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/cargo-fmt b/bin/cargo-fmt index 6219148844..f4fc0d11c0 120000 --- a/bin/cargo-fmt +++ b/bin/cargo-fmt @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/cargo-miri b/bin/cargo-miri new file mode 120000 index 0000000000..f4fc0d11c0 --- /dev/null +++ b/bin/cargo-miri @@ -0,0 +1 @@ +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/clippy-driver b/bin/clippy-driver index 6219148844..f4fc0d11c0 120000 --- a/bin/clippy-driver +++ b/bin/clippy-driver @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rls b/bin/rls new file mode 120000 index 0000000000..f4fc0d11c0 --- /dev/null +++ b/bin/rls @@ -0,0 +1 @@ +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rust-analyzer b/bin/rust-analyzer index 6219148844..f4fc0d11c0 120000 --- a/bin/rust-analyzer +++ b/bin/rust-analyzer @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rust-gdb b/bin/rust-gdb index 6219148844..f4fc0d11c0 120000 --- a/bin/rust-gdb +++ b/bin/rust-gdb @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rust-gdbgui b/bin/rust-gdbgui index 6219148844..f4fc0d11c0 120000 --- a/bin/rust-gdbgui +++ b/bin/rust-gdbgui @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rust-lldb b/bin/rust-lldb index 6219148844..f4fc0d11c0 120000 --- a/bin/rust-lldb +++ b/bin/rust-lldb @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rustc b/bin/rustc index 6219148844..f4fc0d11c0 120000 --- a/bin/rustc +++ b/bin/rustc @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rustdoc b/bin/rustdoc index 6219148844..f4fc0d11c0 120000 --- a/bin/rustdoc +++ b/bin/rustdoc @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rustfmt b/bin/rustfmt index 6219148844..f4fc0d11c0 120000 --- a/bin/rustfmt +++ b/bin/rustfmt @@ -1 +1 @@ -.rust-1.98.1.pkg \ No newline at end of file +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/bin/rustup b/bin/rustup new file mode 120000 index 0000000000..f4fc0d11c0 --- /dev/null +++ b/bin/rustup @@ -0,0 +1 @@ +.rustup-1.29.2.pkg \ No newline at end of file diff --git a/docs/contributing.md b/docs/contributing.md index 41a665fb67..68d2cf0d67 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,8 +1,9 @@ # Contribution workflow The repository pins just 1.58.0, Node.js 24.18.0, pnpm 11.8.0, Lefthook 2.2.2, -and Rust 1.98.1 -(including Cargo, rustfmt, and Clippy) with [Hermit](https://cashapp.github.io/hermit/). +and rustup with [Hermit](https://cashapp.github.io/hermit/). `rust-toolchain.toml` +pins Rust 1.98.1 (including Cargo, rustfmt, and Clippy), which rustup installs +under `~/.rustup` on first use. No global tool installation is required: `bin/hermit` bootstraps Hermit and tools are downloaded on first use. Desktop development still requires the [Tauri platform prerequisites](https://v2.tauri.app/start/prerequisites/). @@ -240,7 +241,7 @@ Buzz library are described in [Agents](agents.md#community-inventory). ## Running the app -Hermit pins just, Node.js 24, pnpm 11.8.0, and Rust in `bin/`; no global tool +Hermit pins just, Node.js 24, pnpm 11.8.0, and rustup in `bin/`; no global tool installation is needed. From the repository root, activate the pinned environment: ```sh diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..86507a6ea7 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.98.1" +profile = "minimal" +components = ["clippy", "rustfmt", "rust-analyzer"] From c5d7bb435e217880a0ac1c222e45a4aff5095e11 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 29 Oct 2026 17:19:16 +1100 Subject: [PATCH 2/3] build: install the pinned Rust toolchain explicitly before first use rustup does not serialize installs into the shared ~/.rustup, so concurrent first use of the proxies (an editor plus a terminal, or parallel worktrees) can leave a component's binaries missing while rustup reports it installed. Run `rustup toolchain install` from the worktree bootstrap before pnpm and any Rust consumer start, document running it after cloning and after a rust-toolchain.toml change, and document reinstalling the toolchain to repair a damaged install. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Matt Toohey --- AGENTS.md | 3 ++- docs/contributing.md | 16 +++++++++++++--- scripts/bootstrap-worktree.sh | 4 ++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 25640c2a83..14f0e9351e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,7 +73,8 @@ scripts/bootstrap-worktree.sh /absolute/path/to/source/checkout Do not start development before bootstrap completes. The script copies the git-ignored `.env.local` without overwriting an existing target, then uses that -worktree's Hermit proxy to run `bin/pnpm install --frozen-lockfile`. Do not copy +worktree's Hermit proxies to run `bin/rustup toolchain install` and +`bin/pnpm install --frozen-lockfile`. Do not copy other ignored paths: Keychain credentials and pnpm's package cache are machine-shared, while dependencies and build output are regenerated. Git hooks need no per-worktree setup; see [Git hooks](docs/contributing.md#git-hooks). diff --git a/docs/contributing.md b/docs/contributing.md index 68d2cf0d67..f5fdfeac8c 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -3,7 +3,7 @@ The repository pins just 1.58.0, Node.js 24.18.0, pnpm 11.8.0, Lefthook 2.2.2, and rustup with [Hermit](https://cashapp.github.io/hermit/). `rust-toolchain.toml` pins Rust 1.98.1 (including Cargo, rustfmt, and Clippy), which rustup installs -under `~/.rustup` on first use. +under the machine-shared `~/.rustup`. No global tool installation is required: `bin/hermit` bootstraps Hermit and tools are downloaded on first use. Desktop development still requires the [Tauri platform prerequisites](https://v2.tauri.app/start/prerequisites/). @@ -12,9 +12,18 @@ From the repository root: ```sh source bin/activate-hermit # bash/zsh; fish: source bin/activate-hermit.fish +rustup toolchain install just --list ``` +Run `rustup toolchain install` after cloning and after `rust-toolchain.toml` +changes, before an editor or parallel commands start the Rust proxies. rustup +does not serialize installs, so concurrent first use can leave the toolchain +missing a component's binaries while rustup reports it installed (for example, +`cargo clippy` fails although `rustup component add clippy` says it is up to +date). To repair it, reinstall the toolchain: +`rustup toolchain uninstall 1.98.1 && rustup toolchain install`. + Without activation, use `bin/just`, `bin/pnpm`, or `bin/cargo` from the root; these proxies supply the pinned environment to child commands too. For example, `bin/just web` needs no shell setup. With activation, commands also work from @@ -172,8 +181,9 @@ scripts/bootstrap-worktree.sh /absolute/path/to/source/checkout ``` The idempotent script copies the source checkout's git-ignored `.env.local` -without overwriting an existing target, then uses the new worktree's Hermit proxy -to run `bin/pnpm install --frozen-lockfile`. It rejects checkouts from another +without overwriting an existing target, then uses the new worktree's Hermit +proxies to run `bin/rustup toolchain install` and +`bin/pnpm install --frozen-lockfile`. It rejects checkouts from another repository. Keychain credentials and pnpm's package cache remain machine-shared; do not copy private keys, `node_modules`, build output, `.npmrc`, or other ignored files. Git hooks need no per-worktree step; see [Git hooks](#git-hooks). diff --git a/scripts/bootstrap-worktree.sh b/scripts/bootstrap-worktree.sh index 18fe59340f..08b8296883 100755 --- a/scripts/bootstrap-worktree.sh +++ b/scripts/bootstrap-worktree.sh @@ -29,4 +29,8 @@ else echo "No .env.local to copy from $source" fi +# Install the pinned toolchain before editors or parallel commands use the Rust +# proxies: concurrent first-use installs into the shared ~/.rustup can corrupt it. +(cd "$target" && bin/rustup toolchain install) + "$target/bin/pnpm" install --dir "$target" --frozen-lockfile From 87c4ad1c93d0bcffaba50618df0f6a542e61e91e Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 29 Oct 2026 20:32:18 +1100 Subject: [PATCH 3/3] test: copy rust-toolchain.toml into the hook-test fixture The Rust proxies now resolve through rustup, which picks the toolchain from the working directory. The hook-test fixture runs outside the repo, so without the toolchain file rustup falls back to the default toolchain: it fails where none is configured (a Hermit-only machine) and otherwise formats with an unpinned rustfmt. Copy rust-toolchain.toml with the other configuration files so the fixture's rustfmt job uses the pin, as the production hook does. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Matt Toohey --- tests/integration/hooks.test.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/hooks.test.mjs b/tests/integration/hooks.test.mjs index 01b073d842..50d11292be 100644 --- a/tests/integration/hooks.test.mjs +++ b/tests/integration/hooks.test.mjs @@ -71,6 +71,8 @@ function fixture(t, { install = true } = {}) { "package.json", "lefthook.yml", "justfile", + // rustup selects the pinned toolchain from the working directory. + "rust-toolchain.toml", "scripts", ...biomePlugins.map((plugin) => path.normalize(plugin)), ];