Make the Rust toolchain a documented prerequisite, and stop uv racing rustup - #172
Merged
Conversation
A fresh checkout of master fails its first sync with an error that names neither
rustup's on-demand install nor the concurrency that broke it:
error: component download failed for cargo-x86_64-unknown-linux-gnu:
could not rename downloaded file ... No such file or directory (os error 2)
`dev` is one of uv's default groups and pulls in `apps`, so a plain `uv sync` --
or any `uv run`, which revalidates path deps -- builds the maturin crates under
rust/. On a machine that does not yet have the toolchain rust-toolchain.toml
pins, each crate's cargo call asks rustup to install it, and rustup's install
path is not safe against concurrent invocations: whichever call wins clears
$RUSTUP_HOME/downloads and the others die renaming a half-downloaded component.
CI already installs the toolchain in a step of its own for this exact reason
(.github/workflows/pytest.yml); nothing carried that over to dev machines.
Two parts:
* README and CLAUDE.md name rustup as a prerequisite and give the argless
`rustup toolchain install`, which takes channel, profile and components from
rust-toolchain.toml so the pin stays in one place. Fixing machine state is
what covers every concurrent cargo caller rather than uv's builds alone --
rust-analyzer's `cargo metadata` on the workspace races identically, and no
uv setting reaches it.
* concurrent-builds = 1 closes the uv-driven half for whoever skips the docs.
Measured on master (two maturin crates), max crates building at once: 2 with no
setting, 1 with the key, 1 with UV_CONCURRENT_BUILDS=1, and 2 again under
UV_CONCURRENT_BUILDS=8 -- the env var overrides the key, and the key is not
merely parsed. Serializing costs next to nothing here: the crates share one
cargo workspace, so their builds already contend on rust/target's build-dir
lock.
pytest -m "not expensive": 956 passed. pyright: 0 errors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A missing toolchain is what arms the race, so it is not a fresh-checkout-only problem: the next bump to rust-toolchain.toml's `channel` re-arms it for everyone at once, and the install step reads as one-time without this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jtoman
approved these changes
Aug 18, 2026
Contributor
I don't think I skipped any docs okay!?! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The failure
Nothing in that message names the actual cause.
devis one of uv's default groups and pulls inapps, so a plainuv sync— or anyuv run, which revalidates path dependencies — builds the maturin crates underrust/. On a machine that doesn't yet have the toolchainrust-toolchain.tomlpins, each crate's cargo call asks rustup to install it, and rustup's install path is not safe against concurrent invocations: whichever call wins clears$RUSTUP_HOME/downloadsand the others die renaming a half-downloaded component.CI already installs the toolchain in a step of its own for exactly this reason (
.github/workflows/pytest.yml). Nothing carried that over to dev machines.What's here
README.md—rustupjoins the Prerequisites list, with a section giving the arglessrustup toolchain install --no-self-update(it takes channel, profile and components fromrust-toolchain.toml, so the pin stays in one place), the note that a distrocargopackage is not enough, and the--no-devescape hatch for anyone not working on the Rust side.CLAUDE.md— the same, on the pre-validation pass, since the sync recipe there is the command that trips this.pyproject.toml—concurrent-builds = 1under[tool.uv], closing the uv-driven half of the race for whoever skips the docs.The docs matter more than the setting: fixing machine state covers every concurrent cargo caller, whereas
concurrent-buildsonly covers builds uv itself drives. rust-analyzer runningcargo metadataon the workspace the moment the repo opens in an editor races identically, and no uv setting reaches it.Measured, not assumed
Max crates building at once, on master's two maturin crates:
[tool.uv] concurrent-builds = 1UV_CONCURRENT_BUILDS=1UV_CONCURRENT_BUILDS=8So the config key genuinely applies rather than merely parsing, and the env var overrides it. Serializing costs next to nothing here: the crates share one cargo workspace, so their builds already contend on
rust/target's build-dir lock.Verification
pytest -m "not expensive"— 956 passed, 12 deselectedpyright— 0 errorsNote for reviewers
This bites whenever the pinned toolchain is missing, not only on a fresh checkout — so the next bump to
rust-toolchain.toml'schannelre-arms it for the whole team at once. That's the case worth keeping in mind if this text gets edited down further.🤖 Generated with Claude Code