Repository navigation
fix(ci): unbreak main and nightly, and close the dependabot wasmtime gap - #2715
Conversation
The nightly Security Analysis job installed cargo-geiger with no version,
so it resolved to the newest release. cargo-geiger 0.14.0 shipped on
2026-10-12 with its MSRV raised to 1.97, and the job builds it from
source against the toolchain pinned at 1.95.0. Nightly run 259 went red
the next morning on a tree that had not changed:
error: cannot install package `cargo-geiger 0.14.0`, it requires
rustc 1.97 or newer, while the currently active rustc version is 1.95.0
This is the same failure mode the sibling advisories job already guards
against, and the reason rust-toolchain.toml pins the compiler at all: a
same-day upstream release must not be able to break a tree nobody
touched. Pin the tool to 0.13.0 (MSRV 1.85) and bump it deliberately
with the toolchain.
Also correct the threat-model tooling table, which listed cargo-geiger
as running on every PR; it has only ever run in the nightly workflow.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
bashkit | 101ee53 | Commit Preview URL Branch Preview URL |
Oct 13 2026, 09:41 AM |
|
Verified the pin locally against the exact toolchain the job uses, since nightly itself only runs on a schedule: Same Generated by Claude Code |
The "/" cargo entry ignores wasmtime, because the pin is load-bearing twice over: the `cpython` feature loads a module precompiled by build.rs, which only loads into the identical wasmtime version, and 48.x is the newest line whose MSRV the pinned rustc 1.95.0 satisfies. `ignore` is per-entry, and neither the fuzz entry nor the example workspaces entry carried it. The fuzz lockfile resolves the root workspace, so dependabot rewrote the root `=48.0.5` pin straight through that gap and opened #2708, where every rust job died at resolution: wasmtime@49.0.2 requires rustc 1.96.0 Either upgrade rustc or select compatible dependency versions `examples/hyperlight/host` had the same exposure from the other entry: it pins its own wasmtime "48" and is built by required CI on the same pinned toolchain. Left alone, both reopen weekly, the same way the monty ignores were added after #2296 and #2297 came back. Add the ignore to both entries, and a test that fails whenever any cargo entry can propose a wasmtime bump — which entry reaches which manifest is not worth re-deriving each time one is added, and this is the manual step the file already exists to catch.
Record both halves of the fix and the rule they share: an unpinned tool against a pinned compiler defeats the point of pinning the compiler.
|
It is a race, not a dd regression. The oracle loop writes the whole input to GNU dd's stdin and unwraps: host.stdin.take().unwrap().write_all(input.as_bytes()).unwrap();One of the eight cases is With no load the parent nearly always wins, which is why it passed on main for f16888d and on the six dependabot PRs earlier today. Under a loaded runner it loses, and it will keep reddening unrelated PRs until the write tolerates EPIPE. Proposed patch — EPIPE is the contract under test here, not a failure, and every assertion still runs (no skip, no // GNU dd with count=0 exits without reading stdin, so this write races
// the child's exit and can fail with EPIPE. That is the case being
// exercised, not an error; the assertions below still compare output.
let mut stdin = host.stdin.take().unwrap();
match stdin.write_all(input.as_bytes()) {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {}
Err(e) => panic!("writing dd stdin for {args}: {e}"),
}
drop(stdin); // close before wait_with_output, as the temporary didNo re-run was needed: 103ffc0 has been superseded twice, so CI re-ran from scratch on 4506599 and again on 2484d9f. I'll send the patch above as its own PR rather than widening this one. Generated by Claude Code |
`dd_normal_copies_match_gnu` writes each input to GNU dd's stdin and
unwraps the result. One of its eight cases is
("abcde", "ibs=4 count=0 conv=sync status=none")
and GNU dd with count=0 exits without reading a byte, so that write only
succeeds if it reaches the pipe buffer before the child exits. Whoever
wins the race decides whether the test passes:
panicked at crates/bashkit/tests/integration/dd_tests.rs:65:14:
called `Result::unwrap()` on an `Err` value:
Os { code: 32, kind: BrokenPipe, message: "Broken pipe" }
Unloaded, the parent nearly always wins, which is why the test passed
when it landed. On a loaded runner it loses: main is red on fa78d9b and
0f23a91 with exactly this failure, as were two commits of this branch.
Reproduced deterministically against the same dd (coreutils) 9.4 by
giving the child a head start; only the count=0 case is affected, the
other seven read to EOF.
EPIPE here is the contract under test, not an error, so accept it and
keep going. Every assertion still runs: no test is skipped, ignored or
quarantined, and a write failing for any other reason still panics, now
naming the case.
|
Correction to my previous comment: the dd patch is in this PR (101ee53), not a separate one. Two things changed the call.
So this is a base-branch failure, and the rule for one is to port the fix into the PR rather than wait on it. Holding it back would have left this PR, main, and everything queued behind them red against a one-line race I had already diagnosed. Nothing about the diagnosis changed — same Generated by Claude Code |
|
Correcting the record on this one: the cargo-geiger half did not work. The description above claims nightly is green again; it is not. The dd and wasmtime halves did land correctly. I dispatched nightly on the merged main (run 260) to verify, and
The real fix is one line: - name: Install cargo-geiger
uses: taiki-e/cache-cargo-install-action@9ee83... # v3
with:
tool: cargo-geiger@0.13.0
locked: trueComing as a follow-up PR, with a test ( Generated by Claude Code |
…#2719) The pin added in #2715 did not work. cache-cargo-install-action has no `version` input, and GitHub Actions treats an unknown input as a warning rather than an error, so `version: 0.13.0` was dropped and the step kept installing the newest release. Nightly run 260, on the commit meant to fix this, failed exactly as run 259 did with "cargo-geiger 0.14.0 requires rustc 1.97" plus a buried "Unexpected input(s) 'version'". The version belongs in `tool:` as `name@version`; the action's own pre.sh splits on `@` and otherwise sets version=latest. The earlier log's `INPUT_VERSION: 0.14.0` was that computed value, not an input we passed, which is what made the wrong spelling look plausible. Adds scripts/tests/test_pinned_ci_tools.py covering both halves: a source-built tool on the pinned toolchain with no @Version, and any input this action silently drops. Jobs on the nightly toolchain are exempt, since nightly outruns any released MSRV - which is why cargo-fuzz needs no pin.
What changed
Three CI failures, two causes, all of them things that broke a tree nobody
had changed.
dd_normal_copies_match_gnuwrites to GNU dd'sstdin and unwraps; with
count=0dd exits without reading, so the writeraces the child's exit. EPIPE is now accepted for what it is.
Security Analysisjob installs a pinnedcargo-geiger(0.13.0) instead of the newest release, so the unsafe-codereport is produced again after four days of the job dying before it ran.
wasmtimefrom thefuzz or example-workspace entries. The
/entry already ignored it; theother two did not, and
ignoreis per-entry.Also corrects the threat-model tooling table, which advertised
cargo-geigeras running on every PR. It has only ever run nightly.
Why
The dd oracle (main). One of its eight cases is
("abcde", "ibs=4 count=0 conv=sync status=none"), and GNU dd withcount=0exits without reading a byte, so the write only succeeds if it reaches the
pipe buffer first:
Unloaded the parent nearly always wins, which is why it passed when it landed.
On a loaded runner it loses, and it is already reddening main:
dd_normal_copies_match_gnu0f23a917(#2712 merge)462f367c(#2710 merge)fa78d9b2(#2705 merge)cargo-geiger (nightly). The install step named the tool with no version,
so it resolved to the newest release while the job builds it from source
against the toolchain pinned at 1.95.0. 0.14.0 shipped 2026-10-12 with its
MSRV raised to 1.97, and run 259 went red the next morning:
wasmtime (dependabot). The pin is load-bearing twice over — the
cpythonfeature loads a module precompiled by
build.rs, which only loads into theidentical wasmtime version, and 48.x is the newest line the pinned rustc
satisfies. The
/entry says exactly that and ignores it, but the fuzzlockfile resolves the root workspace, so dependabot rewrote the root
=48.0.5pin through the gap in the fuzz entry and opened #2708, where everyrust job died at resolution with
wasmtime@49.0.2 requires rustc 1.96.0.examples/hyperlight/hosthad the same exposure from the other entry: it pinsits own wasmtime
"48"and is built by required CI on the same toolchain.Left alone both reopen weekly, the way the monty ignores were added only after
#2296 and #2297 came back.
The shared lesson: an unpinned tool against a pinned compiler defeats the
reason
rust-toolchain.tomlpins the compiler at all.Before / After
dd — reproduced deterministically against the same
dd (coreutils) 9.4bygiving the child a head start. Only the
count=0case is affected; the otherseven read to EOF:
After, EPIPE is accepted and the case proceeds to its assertions. No test is
skipped, ignored or quarantined; a write failing for any other reason still
panics, now naming the case.
cargo-geiger — before: run 259,
Install cargo-geigerfails in 1s, so theaudit and its report upload are skipped and the job is red. After, the pin
resolves under the job's own toolchain:
wasmtime — the new test fails on the old config, naming both gaps, and
passes on the new one:
The geiger pin only selects which binary builds — the audit invocation, its
--all-featuresflag and itscontinue-on-errorposture are untouched, sowhat the job reports is unchanged. It was already informational and
non-blocking; this restores a signal that had gone silent rather than adding a
gate. No library or CLI code changes anywhere in this PR.
Risk
entry. No production code.
write; every comparison it made before, it still makes.
is the intended trade and the comments say so, next to the equivalent note in
rust-toolchain.toml. Left unpinned, both re-break on the next upstream MSRVbump.
until the toolchain reaches 1.97.
including fixtures that do not use wasmtime. Which entry reaches which
manifest is not worth re-deriving each time one is added; lift them together.
blocked, not a casualty of it.
Checklist
test_no_cargo_entry_can_propose_a_wasmtime_bumpadded to the dependabot-config suite that required CI already runs
(
ci.ymldiscoversscripts/tests), shown failing before and passingafter. The dd change is itself a test-harness fix with its failure
reproduced above. The geiger half is workflow configuration with no code
under test, verified by the install above plus
just check-workflow-parity("release workflows match CI for: node,python; rust toolchain pins agree") and
just check-okf("knowledge: OKFv0.2 conformant").
surface.