Repository navigation
ci: install pre-commit and the pinned rust toolchain into the tend sandbox - #6334
Conversation
`task lint` is `pre-commit run --all-files` followed by clippy, and `pre-commit` is on neither the runner's PATH nor the sandbox's, so the lint gate `CLAUDE.md` documents aborts on its first command in every tend session. `uv tool install` puts it in `~/.local/bin`, first on the sandbox PATH, alongside the `cargo-insta`/`cargo-nextest` entries added in #6251.
prql-bot
left a comment
There was a problem hiding this comment.
pre-commit lands on the sandbox PATH as intended — 0.46 s, ~/.local/bin/pre-commit — and uvx tend init on this branch reproduces the eight workflow files byte-identically. But running the gate end to end shows it still doesn't pass in a fresh sandbox: pre-commit run --all-files exits 1, so task lint never reaches clippy.
The hook that fails is fmt, and nothing is misformatted:
Check for formatting with cargo fmt......................................Failed
- hook id: fmt
- exit code: 1
error: component download failed for cargo-x86_64-unknown-linux-gnu: could not rename 'downloaded' file from '/home/tend-sandbox/.rustup/downloads/<hash>.partial' to '/home/tend-sandbox/.rustup/downloads/<hash>': No such file or directory (os error 2)
rust-toolchain.toml pins 1.97.1 and the sandbox home ships only stable, so the first cargo call triggers a rustup auto-install. The fmt hook in r0x0d/pre-commit-rust leaves pass_filenames at its default, so pre-commit splits this repo's 114 .rs files across the runner's 4 cores and launches one cargo fmt per chunk — each racing the same auto-install into the shared ~/.rustup/downloads and clobbering the others' .partial files. Install the toolchain once, serially (cargo --version, 11 s), and the same hook passes.
Pushing a cargo --version entry to sandbox_setup: for that. It's the fix that stays inside this PR's subject, and it's close to free in aggregate: any session that touches Rust pays that auto-install anyway, just later and mid-task. The deeper fix is require_serial: true on the fmt hook in .pre-commit-config.yaml, which would remove the race for any contributor with a cold rustup rather than only in the sandbox — but that changes lint behaviour for everyone, so it's a maintainer's call rather than something to fold into a sandbox-setup PR.
Second, smaller point: the cold hook build measured 233.7 s here against the description's 212.7 s, and because ~/.cache/pre-commit lives in the sandbox home it's paid by every session that lints, not just the first. Both numbers are past the agent's 120 s default command timeout — the fmt hook on its own ran past it during this review — so a session running task lint has to extend the timeout or it sees the command killed mid-build, with no way to distinguish that from a broken gate. The .config/tend.yaml comment is the only place that cost is recorded and nothing loads it at session start, so it's worth a line in running-tend as a separate follow-up.
pre-commit's `fmt` hook passes filenames, so it starts one `cargo fmt` per core. The sandbox home ships only `stable` while rust-toolchain.toml pins 1.97.1, so each of those races the same rustup auto-install and they clobber each other's partial downloads — the hook fails on a component-download error and `task lint` never reaches clippy. Installing the toolchain once, serially, costs ~11s.
task lint— the lint commandCLAUDE.mddocuments — ispre-commit run --all-filesfollowed by clippy, and it doesn't run in a tend session today. This adds the twosandbox_setup:entries that make it run, beside thecargo-insta/cargo-nextestentries from #6251 that fixed the same shape of gap for the test loop.pre-commitis absent from the runner's PATH and the sandbox's alike, so the gate aborts on its first command in every session.uv tool install pre-committakes 0.46 s —uvis already provisioned, so nothing is built — and puts the binary in~/.local/bin, first on the sandbox PATH.The pinned Rust toolchain isn't installed either, and that breaks the lint gate a second time, less obviously.
rust-toolchain.tomlpins1.97.1; the sandbox home ships onlystable. Thefmthook inr0x0d/pre-commit-rustleavespass_filenamesat its default, sopre-commitsplits this repo's 114.rsfiles across the runner's cores and launches onecargo fmtper chunk — each racing the same rustup auto-install into the shared~/.rustup/downloadsand clobbering the others'.partialfiles:The hook fails,
pre-commitexits 1, andtask lintnever reaches clippy — so installingpre-commitalone would have left the gate red for a reason that has nothing to do with the repo's formatting.cargo --versioninstalls the pin once, serially, in 11 s, and is a cost every session that touches Rust pays anyway.With both entries,
pre-commit run --all-filespasses all 14 hooks —typos,prettier,ruff,ruff-format,clang-format,cargo fmt,actionlint,lychee,no-dbgand the rest — exits 0, and leaves the working tree byte-identical. The hook environments are deliberately not built insandbox_setup:;pre-commitbuilds them on first use, 233.7 s cold for this repo's hooks, so only a session that actually lints pays that and a session that never lints pays a second.The cost of the gap is concrete rather than theoretical: the 2026-09-18 nightly opened #6333 and closed with "
task lintcouldn't run:pre-commitisn't on PATH in the sandbox. I ran the prettier and rustfmt hooks directly (both clean), but the other hooks —typos,ruff— went unrun". That is the documented gate going unrun on a PR the bot authored, withpre-commit.cion the PR as the only thing that would have caught it.The two edited-file groups are one change:
sandbox_setup:is inlined into each generated workflow, so.config/tend.yamland the eighttend-*.yamlfiles move together. The workflow diff is the three added lines and nothing else — no version stamp moved, sinceuv tool run tend@latest initregenerated at the 0.2.10 already pinned by #6330.The config's own comment block said
cargo llvm-covandpre-commitare both off the sandbox PATH; it now names onlycargo llvm-cov, since a stale comment there is the durable prose a later session reads as ground truth.No
CHANGELOG.mdentry: this changes the bot's CI environment, not anything a PRQL user sees.Why a config entry rather than a note in the skill
#6235 proposed recording the equivalent gap for
cargo-insta/cargo-nextestas guidance in therunning-tendoverlay, and was closed with "we need to fix the underlying problem, am working on it" — which became #6251, thesandbox_setup:entries this one sits beside. The same reasoning applies here, andrunning-in-cisays the same thing from the other direction: "A missing gate tool is reported, not worked around: propose the entry and say the gate went unrun rather than substituting a weaker command."cargo llvm-cov— the other tool missing fromtask prqlc:pull-request— is deliberately not included, keeping this PR to one concern. The 2026-09-08 nightly hit its absence and chose not to propose an entry, on the grounds that.config/tend.yamlalready documents it and the uninstrumented suite covers the same tests. Whether coverage is worth provisioning is a separate question from whether the lint gate should run at all.Verification on this branch, beyond the hooks above:
uv tool run tend@latest checkreports 9PASSand the standingSKIPonimmutable-releases(needs repo admin), anduv tool run tend@latest initregenerates the eight workflow files byte-identically, so config and workflows are in sync.Two open questions this doesn't settle
The
fmtrace has a deeper fix.require_serial: trueon thefmthook in.pre-commit-config.yamlwould remove it for any contributor with a cold rustup, not just the sandbox — a fresh clone hits the same race, sincerust-toolchain.tomlpins a version a new contributor is unlikely to have. It changes lint behaviour for everyone though, so it's left as a maintainer's call rather than folded into a sandbox-setup PR.The first
task lintof a session runs past the agent's default command timeout.~/.cache/pre-commitlives in the sandbox home, so the 233.7 s cold build is paid by every session that lints, not just the first — against a 120 s default. A session that doesn't extend the timeout sees the command killed mid-build and can't distinguish that from a broken gate. The comment in.config/tend.yamlis the only record of the cost and nothing loads it at session start, so a line inrunning-tendis worth a separate follow-up.Evidence trail
Recorded in the
review-runstracking issue #6266:command -vanswers MISSING for bothcargo-llvm-covandpre-commit, and neither is a blocked stub — they are absent from the runner's~/.cargo/bintoo, sosandbox_setup:cannot recover them without also installing them.task lintunrunnable and namestyposandruffas ungated.fmtrace measured directly, numbers above.Gate assessment: High confidence, structural (no decision point — neither
pre-commitnor the pinned toolchain is present in any session), occurrence 3; change type is a targeted fix — three lines of config plus its regen. Both gates pass.