The charset guard went green for months on unchecked files

7–11 minutes
measured on my own machine
The charset guard went green for months on unchecked files

I wanted one rule that stands in one place and holds everywhere: real UTF-8, no ASCII substitutes for umlauts, no HTML entities beyond the three that are allowed. Write it once in the instruction file, and every file written afterward obeys it. That was the idea.

A guard that checks character encoding reported 130 violations at once — after months of going green. Not because someone suddenly started writing wrong, but because the rule had never reached the source code at all.

I believed two things, and both were wrong. First, that a rule written in the instruction file applies to everything written after it. Second, that a guard reporting green has checked something. A green check felt like coverage. It was only the absence of a failure on the files the guard happened to look at.

The guard checked text against a list of German function words. What was not on the list, it did not see. A curated blocklist knows only the cases someone already noticed — and nobody thinks of compound words. The scope was prompts, rule files, and reports — all Markdown. Comments in source code were never in it, though this house keeps almost all of its reasoning there: 21.368 lines of production code, about a third of it comment. So the rule covered the prose it was pointed at and missed two thirds of the prose that mattered.

The day the guard first caught me

It did not fail on an abstraction. The first violation the new guard caught was mine. While fixing five audit findings, I wrote ASCII substitutes into my own comments twice, in the same session that had just fixed exactly that kind of violation in the monorepo rule file. Nothing caught me — re-reading did. That does not scale, and it is the whole reason the guard had to reach the source at all.

6b9b134f · 2026-09-02

Zeichensatz: die Regel erreichte den Quelltext nicht — 130 Verstöße, jetzt bewacht

Charset: the rule never reached the source code — 130 violations, now guarded

Zero tolerance, no exemption list — that was the actual work. You cannot switch on a guard that is red on the day it ships; a guard that is red at birth gets turned off, not obeyed. So all 130 places were fixed first, then the guard went up. An exemption list of 82 tolerated files would have been the exact shape this house warns against: a list that absolves works in the direction nobody looks.

  1. 2026-08-23The encoding guard knew six words it should have and wrongly flagged a seventh — all-caps text where the German sharp-s has no common uppercase form. I added the missing words and carved out the false alarm, with a test pinning the boundary.
  2. 2026-08-23I wrote up the day: a metric had claimed one channel delivered 14 drafts and none usable. I read the 14 cards. They were all fine — and not one had been rejected by a human.
  3. 2026-09-02I pointed the guard at source-code comments for the first time and measured what it had been missing: 130 violations across 82 files. I cleaned all of them before switching the guard on, so it was green the moment it went live.
  4. 2026-09-06The guard caught me again: two ASCII substitutes in a paragraph I had written twenty minutes earlier. The house rule demands real UTF-8; I did not catch it myself.
  5. 2026-09-06A separate encoding trap: a sign-up form had been dead since its first deploy, because WordPress had re-encoded an ampersand in the inline script into its HTML entity — a SyntaxError that killed the whole script, not just the line.
  6. 2026-09-06Two more ASCII substitutes surfaced in an archive file — carried in by my own commit from that same morning. The test found them on a full suite run, and only the control check (does it fail without my change?) stopped me filing them as old debt.
The steps in the order they happened — the guard catching its own author more than once.
The steps in the order they happened — the guard catching its own author more than once.

There was a second layer to the same problem, and it cost real function. A sign-up form had been dead since its first deploy — twelve days. Nobody found it by clicking; a checker that parses the delivered page rather than the repo source found it. The source was flawless, so every check that read the source would have reported green. WordPress encodes a space-surrounded ampersand inside an inline script as its HTML entity, the browser reads a SyntaxError, and the error kills the entire script: the form, a dismiss counter, and the trigger all went down together.

c987f7cc · 2026-09-06

Das Anmeldeformular nimmt wieder Adressen an — kein Kaufmanns-Und mehr im Skript

The sign-up form accepts addresses again — no more ampersand in the script

The fix was to remove the cause, not the symptom. The reported character was a red herring: measured with node –check against both readings of the data: URI, as UTF-8 and as Latin-1, the SyntaxError sat at the entity in both cases, not at the flagged character. The ampersand is gone from that file entirely now — nested if statements instead of a short-circuit, behaviorally identical, short-circuit included, with a note in the script head so nobody pulls them back together and kills the form again.

$ git log --since="21 days ago" -i --grep=zeichensatz --pretty=format:"%h %s" -- BlogFactory
(keine Ausgabe)
No output: the encoding work lived in another project’s tree, not this one’s — another way the scope and the rule failed to line up.
What the record supports — and what it does not. The guard is up; the vocabulary stays open.
What the record supports — and what it does not. The guard is up; the vocabulary stays open.

Not solved. The rule is guarded now, but the vocabulary stays open, and the guard still catches only what someone thought of. Replacing it properly needs a dictionary instead of a list. Until then, adding words is the price, and it gets paid in the open rather than hidden. What has been ruled out is the comfortable story that a green check means coverage — it does not, when the check is pointed at the wrong files.

Measured over a 14-day window, 2026. Five commits in this thread, each checked by hand against the record; the encoding work lived across two projects, and the source tree that carried the form bug was flawless while the delivered page was dead.

07a7334a · 6b9b134f · 72d98bf2 · c987f7cc · d6bb578c

Georg Singer

Georg Singer

I automate my own projects in public and write down what each one cost. Physics degree, doctorate in computer science, nine systems in production on hardware I own, all of it built in my own time.

More from the log