When a Fix Breaks Its Users
The last finding of the part was not in any of the four tools. It was a self-inflicted wound: a small, correct fix to how the language reports its command-line arguments quietly broke a program written in the language — and not just any program, but the very site generator that publishes the language's own documentation. The whole documentation site went to a 404, and it stayed broken until someone visited a page and noticed. This is the dogfood loop running backward, and it is the part's real closing lesson.
The last finding of this part was not in any of the four tools. It was in the language itself, and it arrived the way the worst findings do — not as a failing test but as a report from the outside world that something people relied on had stopped working. The documentation site was returning a 404 for one of its own pages. Nothing in the compiler’s test suite had gone red; the site had simply, silently, stopped building its pages some releases back, and no one had looked until a link was followed and came up empty.
A small, correct fix
The cause traced back to a change that was, on its own terms, entirely right. Earlier in this run, among a batch of small cleanups, the way a program reads its command-line arguments had been made consistent: the list a program receives should contain the arguments the user typed, and nothing else. Before the fix it had inconsistently included the path to the script itself as the first element, which is the kind of small wart that makes every program that reads arguments carry an awkward compensating step. Removing it was correct — it made the language cleaner and every future program simpler. There was nothing wrong with the fix. That is exactly what makes this story worth telling: the damage did not come from a bug. It came from correctly changing a behaviour that a real program had already been written to depend on.
The program that depended on the old wart
The program in question was the site generator — itself written in the language, itself a dogfood from an earlier part — the tool that turns the documentation’s source files into the published website. It read two arguments: an input directory and an output directory. But it had been written back when the argument list still began with the script path, so it carefully skipped that first element before reading the input directory. When the fix removed the script path, that careful skip started skipping a real argument instead: the generator read the output directory as if it were the input, went looking for source files in a folder that had none, found zero pages to convert, and wrote out a site with no content pages in it. It did not crash. It did not print an error. It reported that it had built the site, and it had — an empty one. The compensating step that had been correct against the old behaviour became a bug against the new one, and because it failed by finding nothing rather than by breaking, nothing announced it.
Why nothing caught it
It is worth being honest about every layer that should have stopped this and didn’t. The compiler’s tests all passed, because they test the compiler, not the programs built with it. The site generator had no test that ran it end-to-end and checked that the output actually contained pages. The deployment pipeline rebuilt the site and published it without noticing that the page count had collapsed to zero, because “the build succeeded” and “the build produced anything” are different claims and only the first was being checked. Each of these is the same shape as a lesson from earlier in the part: a program can only surface the pain its own shape touches, and no one had written the shape — a downstream program, run end-to-end, asserting on its real output — that would have touched this. The gap was not in the language this time; it was in the space between the language and the programs depending on it, which nothing was watching.
The dogfood loop, running backward
The whole method of this project has one direction built into it: build a real program, let it hurt, and push the fix upstream into the language so every program benefits. This finding was that same current running the other way. A change upstream in the language flowed downstream into a program that had been written against the old behaviour, and broke it — quietly, because the break took the form of an absence. That is the risk that comes bundled with the reward of dogfooding: once real programs depend on the language, the language can no longer be changed in a vacuum, and “correct in isolation” is not the same as “safe for everything already written.” The reward is worth the risk — the whole language is stronger for having real users, even when those users are its own tools — but the risk is real, and pretending a fix is free because it is correct is how you ship an empty website.
The immediate repair was small: teach the generator that the first argument is now the input directory, no skip required, and the site built its pages again and every documentation page answered. But the repair is not the point. The point is the shape of the failure — a correct change, a silent break, a downstream program nobody was testing end-to-end, found only by a human following a dead link — and it is a fitting close to a part that began by deciding to stop confirming the language was fine and go looking for where it wasn’t. The tools in this part were built to hurt in places chosen in advance. This last pain was not chosen; it was the language’s growing surface area sending back its first bill. That bill is the truest sign the project reached: it means there is finally enough built in the language that changing the language has consequences — which is another way of saying the thing is real.