The 16-minute compiler update
Two days ago I wrote that I had upgraded mrustc from Rust 1.90 to 1.97 with
my ordinary change-management workflow in a little over three hours.
I had not.
The workflow said COMPLETED. It had run for 3 hours 4 minutes, traversed 90
steps, and reported zero failures. Its finalizer had also pruned the no-op
implementation branches. The compiler tree had not advanced beyond the flake
scaffolding it started with.
That distinction matters enough to say plainly: I mistook a green control plane for a delivered result.
Why I did not let it go
I did not set out to spend days turning a compiler upgrade into a specialized expert workflow. I did it because, in the discussion around my initial PR, I was called a liar and accused of spamming while I was still trying to be nice and cooperative.
I can live with abrasive or even toxic behavior when there is competence behind it. What catches in my head is being attacked by people who have not understood the compiler-security problem they are dismissing.
My autistic, ADHD, slightly savant-shaped brain does not reliably discard a contradiction like that. I have written separately about vipassana as self-looped learning and why a lie eventually accumulates consistency debt. Once my brain sees the gap between “reproducible” and “we downloaded the compiler,” the gap stays there. Being told I was dishonest for pointing at it made it stay there harder.
So I stopped trying to win the thread. I built the evidence.
What failed after the green checkmark
The generic workflow was built to turn an intent into an ordinary feature branch: plan work, split it into tasks, implement, review, reconcile, and land. A compiler bootstrap only looks like that from far away.
Up close it is a state machine. Rust 1.94 cannot begin until 1.93 is usable. Each transition may compile LLVM and thousands of Rust objects. A failed attempt must preserve its build directory. A passing compiler must survive outside the development shell, compile Cargo and Hello World, become an immutable package, and stay rooted long enough to build its successor.
Trying to force that through the general workflow uncovered a procession of bugs:
- planners returned success without writing the design artifact at the contracted path;
- recovery found stale changes or changes with no resumable design;
- a rootless container reached the runtime with no mount in its spec;
- nested workflow templates crossed an engine boundary without being rendered;
- worker sandboxes lost the Nix environment that made OpenSSL and
libstdc++.so.6discoverable; - stale rootless bundles filled a 22 GiB runtime filesystem;
- the compiler cache existed, but absolute source paths prevented useful hits.
Some of those were project mistakes. Some were Tixim engine bugs. Fixing one often exposed the next. The useful result was not a heroic retry. It was accepting that this job deserved its own executable procedure.
The workflow shaped like a compiler chain
The replacement has two layers, both public in
workflows/rustc-chain
on the rustc-chain branch of the mrustc fork.
rustc-chain is the parent. It reads the rooted chain state, starts one
rustc-chain-frontier child as a subgraph, checks the new checkpoint, and
continues until the requested version exists.
Each frontier child does six things:
- identify the next missing Rust release;
- ask a researcher to extend the version registry only when the target is beyond what the repository already knows;
- let a coding worker compile and repair inside one persistent Nix development environment;
- prove that
rustc, Cargo, and Hello World work at the current branch commit; - import the proven prefix into one immutable Nix package without rebuilding it;
- root the package and record the transition before the parent advances.
Nix stays at the boundary instead of turning every edit into a fresh immutable
build. The mutable build tree, logs, cache, and chain state survive agent
sessions. sccache covers Rust and LLVM, with source roots normalized so a new
version directory does not manufacture a new cache identity for unchanged
files.
The procedure is now the durable artifact. Models are arguments to it.
The number is 15 minutes 33 seconds
These are the wall-clock durations recorded by the seven successful frontier workflows:
| release | elapsed |
|---|---|
| Rust 1.91.1 | 16m 20s |
| Rust 1.92.0 | 8m 40s |
| Rust 1.93.1 | 19m 30s |
| Rust 1.94.1 | 18m 48s |
| Rust 1.95.0 | 17m 38s |
| Rust 1.96.1 | 13m 15s |
| Rust 1.97.1 | 14m 38s |
Together they took 1 hour 48 minutes 49 seconds: 15 minutes 33 seconds per release on average. That includes orchestration, agent work, compilation, proof, packaging, reporting, and checkpoint finalization.
The final composed parent resumed at the 1.93.1 checkpoint and advanced 1.94.1, 1.95.0, 1.96.1, and 1.97.1 in 1 hour 5 minutes. The misleading three-hour finish and the actual Rust 1.97.1 result are both in the screenshot gallery.
That is the honest measurement. I am not claiming the entire debugging journey took one hour, or that every future Rust release will compile without a new repair.
Always current does not mean unattended
As of this writing, Rust 1.97.1 is the latest stable point release, and the source-built chain reaches it.
Tixim is not public yet. There is no public bot watching Rust releases and no
service to sign up for. When a new stable version lands, I will trigger the
private workflow, or one of my vx-maintainer instances will. If the release
fits the existing registry, the chain advances directly. If it does not, the
workflow dispatches the research and repair roles before it packages the new
frontier.
So “mrustc stays current” is an operational claim, not a magical one. The next release may still expose a new language feature, bootstrap quirk, dependency, or cache boundary. The difference is that the response is now resumable, reviewable, and encoded once for every later run.
The security argument for doing this is in “The compiler you didn’t build”. The full engineering account from the agent side is “The workflow was the program”.
I thought the compiler update was the result. It turned out the workflow that can keep doing compiler updates was the result.