On 18 August I deleted every comment in the Whelk codebase. The commit removed 78,256 lines from 745 files. Of those, 71,660 were comments in hand-written Swift, Rust, Kotlin, Python, TypeScript, shell and YAML. The other 6,596 were generated bindings, which I had to rebuild because their checksum covers doc comments.
The repository was 29 days old.
Where 70,000 comments come from in a month
Agents wrote most of them. Agents write most of the code in Whelk, and I review what they produce. An agent comments the way an anxious new hire does. Every decision gets a paragraph that explains it and then defends it against a question nobody asked.
The paragraphs were good, which made them hard to see as a problem. One sat above an error case in the Swift networking layer. It said the case existed on its own “because the plain one asks ‘is a model loaded?’, and an agent that is not running has no model to load.” That is a clear sentence about a real decision. There were tens of thousands like it. When I counted, comments were 28 percent of every line of Swift, Rust and Kotlin in the repository.
The day before
On 17 August a review caught three comments in the CI configuration. They gave the run time of the Apple build as 15.9, 16.9 and 17.1 minutes. I measured the last thirty-one green runs and the median was 24.1. The lane had gained a simulator boot and a full test run since those numbers were written, and nothing forced the comments to follow.
A budget warning was set from the old figures, and it fired on about half of all Apple runs.
A comment is a second source of truth
Code in this repository answers to a compiler, a test suite and a few dozen gates that fail the build. A comment answers to nothing. It is true on the day it is written, and after that its accuracy depends on every later author noticing it and caring.
A person skims a comment they have seen before. An agent reads every comment on every pass and believes it. A stale comment in an agent’s context works as an instruction, and the agent bends new code to agree with it. I was paying for that on every read, too: more than a quarter of each file an agent opened was prose that no tool had checked.
So the reason for a decision now lives in two places. The commit message carries why the change was made, and the task that asked for the change carries what it was for. A commit message is attached to one diff, and that diff never changes, so the message stays true. git blame gets from any line to its reason in one step. An agent that needs the reason can fetch it. An agent that does not need it pays nothing.
Five comments turned out to be code
The deletion broke the build several times, and each break found a comment that a tool was reading. Five kinds stayed:
- The
swift-tools-versionline at the top of a package manifest. - A marker that tells my copy linter a string is not user-facing.
- The
# lane:filters that tell the pre-push script which tests a diff needs. - The
# Safetydoc that Clippy requires on an unsafe function. - Shebangs.
I found none of them by reading. A gate or a build went red for each one. The generated bindings needed a rebuild because the generator checksums doc comments. All 26 shipped database migrations are pinned by hash, so I had to prove each one byte-identical with the comments stripped from both sides before I could pin them again. One test located a block of YAML by the comment that followed it.
The worst failure had no comment in it. My first attempt also collapsed runs of blank lines, for tidiness. A streaming test fixture ended in two blank lines, and in server-sent events that pair is the frame terminator. The decoder never saw the frame end, the adapter retried against a real host, and twelve tests timed out. The cause was one missing newline in a diff of 70,000 removed lines.
The objection I take seriously
Engineers I respect will say that why-comments are the valuable kind, and that a commit message is too far from the code to do the same job. For a person who reads one file in an editor, that distance is real. I gave up narration for the newcomer who reads a file cold.
I accept that trade for two reasons. Anything an outsider needs explained goes in the docs folder, where it is written for that reader. And when a function needs a paragraph before it makes sense, I now treat the paragraph as a bug report about the function.
They came back
I wrote the rule into the instructions file that every agent reads at the start of a session: code carries no comments. I did not write a check for it.
On 5 October I counted again. The day after the deletion, the Swift, Rust and Kotlin source held 176 comment lines. Seven weeks later it held 1,346. About 1,200 comments had grown back, one sensible paragraph at a time, each written in a session that started with the rule.
A rule written in prose is a comment on the whole repository, and it went stale the same way the others did. The fix is a gate that fails the build when a comment appears outside the five permitted kinds. I have not written it yet.
The Whelk repository is private today and the source opens at launch, so for now you have the numbers and my word. Whelk is private AI and agents that run on hardware you own.