Writing about a thing is not doing it
Hard-WonSix ways a code scanner mistook writing about a practice for following it, and a seventh found inside the check built to prevent the other six.
- Authors
- Leon Mallett, Founder of Captivated Ltd with Claude Code
- Status
- Last confirmed working 28 August 2026 on Rust 1.96.0, Claude Code 2026-08
- Written
- 28 August 2026
- Licence
- Handover-1.0
A scanner was built to score about forty software projects on whether they follow a set of engineering practices — is there a lockfile, are secrets scanned for, is there a privacy policy. It reads files and looks for evidence.
The first thing it found was a 3D model of a biscuit.
The project was a game. The file was Assets/Art/Props/Cookies.FBX. The check was looking
for cookie-consent tooling, and it matched on the filename. It reported a game as having
cookie-consent handling because a character in it eats a biscuit.
That is funny once. The problem is that it happened again five more times, in five different ways, and each way needed a different fix. What follows is each failure and what it cost, because the general lesson is worth much less than the specific shapes.
This is the false-positive half of a pair. The same class of instrument fails the opposite way too — silently missing what it exists to find — which is a separate problem with separate causes, covered in Patterns narrower than reality.
The first defence: an extension allowlist
Cookies.FBX is a binary 3D asset. The fix was to restrict filename matching to files that
could plausibly be source: a fixed list of extensions, everything else ignored.
This is the cheapest defence and the one every scanner eventually gets. It is also the one that gives false confidence, because it only catches evidence in the wrong kind of file. Everything below is evidence in the right kind of file.
The second: restrict to source paths
Build output is a copy of source, so a project with a dist/ directory has every piece of
evidence twice — and some of it transformed in ways that create matches the source never
had. Vendored dependencies are worse: a project that depends on an analytics library has
that library’s entire source tree inside it, and every practice that library follows reads
as a practice the project follows.
The fix is a path restriction, not a content one. Where a file sits says more about whether it is yours than what is in it.
The third: declaring a dependency is not using it
A check for “does this project have error tracking” looked for the tracking library in the dependency manifest. Several projects declared it and never imported it — added during an evaluation, never removed.
A survey found seven projects declaring a schema-validation library and three importing it. The gap is not unusual and it is not dishonesty; a manifest accumulates.
So a dependency-based check has to confirm the dependency is referenced from source. The manifest says what was considered. The source says what was adopted.
The fourth: what kind of evidence is this
Two files can both mention a practice while meaning opposite things. A file called
analytics.rs in a desktop application turned out to fetch the user’s own engagement
metrics from a third-party service and display them — a product feature. Another,
Analytics.tsx, imported a charting library and drew bar graphs.
Both were counted as evidence that the product measures its users. Both are the inverse: analytics shown to users, about their data.
The fix is to classify evidence by kind rather than by name — does this file emit usage data or display it — which needs content, not filenames. And it is worth noting the one that resolved it: the desktop file’s first paragraph read “All data stays on the machine — no third-party analytics.” The file said what it was. Nothing was reading it.
The fifth: code matching code
A check counted files that write diagnostics to the error stream, looking for the token
eprintln!(.
The scanner’s own source contains the line content.contains("eprintln!("). It detected
itself. That put the scanner at three such files instead of one, which mattered because the
threshold was three.
A scanner’s source necessarily contains every token it searches for. It is the worst possible corpus for its own detectors, and any codebase that discusses a technology in strings has a weaker version of the same exposure — a test fixture, a lookup table, an error message naming the thing it warns about.
The fix is to require the token to be invoked rather than mentioned: a match immediately preceded by a quote is inside a string literal. Approximate, and enough.
It cannot be applied blindly. The same scanner looks for {:?} — a debug format specifier
— which lives inside a string by definition. Excluding quoted matches there would disable
the check entirely. Which tokens are code-shaped and which are string-shaped is a judgement
per detector, not a global rule.
The sixth: published content is product, not practice
A project whose entire business is publishing documents about engineering practice will appear to follow every practice it publishes.
The case: a documentation site published a guide to writing operational runbooks. That
guide contained an example line — Last executed: 2026-08-25 (staging, full restore, 4 minutes) — demonstrating how to record that a rollback procedure has been tested.
The scanner read that as the site’s own claim to have tested its own rollback.
This is the trap at its most complete. In every case above the corpus was adjacent to the subject. Here the corpus is the subject, and no amount of looking harder at content distinguishes them, because the content is identical by design.
The fix came from a convention rather than a heuristic: a project’s own documentation lives in one place, and published content lives in another. Directory conventions already draw that line, and using the existing line is more robust than inventing a judgement.
The seventh: a defence failing in the way it defends against
A later check tested whether a documentation set records the commit it was last confirmed
against — a line reading Last confirmed against: <commit>. It was built specifically
with the sixth defence in mind, after all of the above.
It scanned every document in the set for the phrase, took the first match, and found the decision record that specifies the convention rather than the index that carries it. It then searched the whole document for a commit identifier rather than the line the phrase was on, so a decision record quoting a commit would have anchored the entire documentation set to it.
It passed its automated test throughout.
That is the part worth sitting with. The test was a synthetic project built to exercise the check — and it was built by the same process that wrote the check, so it put the line exactly where the check looked. A test written alongside the thing it tests cannot fail in the ways its author did not anticipate, and the aboutness trap is precisely a failure of anticipation.
It was found by using the tool on its own documentation. Not by any test.
What generalises
Any detector operating over a corpus that discusses its own subject has this exposure. Static analysis is the obvious case. So is a spam filter trained on discussions of spam, a content classifier over a corpus about content moderation, and any check written into a codebase that documents its own conventions.
Three things transfer:
Name each defence after the failure that produced it. Seven general principles would have been forgotten. Seven incidents are still remembered, and each one names a distinct mechanism that a principle would have blurred together.
Assume your own corpus is hostile. Not adversarially — incidentally. The documents most likely to mention a practice are documents about that practice, and they cluster exactly where you are looking.
A check and its test written together share a blind spot. Whatever the author did not think of is absent from both. The only reliable cure found here was using the thing for real, on material nobody wrote to satisfy it.