
Three of our sixteen blog posts were about to ship with no analytics tag at all. Not a broken tag, not a wrong measurement ID — the snippet was simply not in the file. The pages rendered correctly, validated, carried their canonical link and their structured data, and would have returned a perfectly ordinary 200.
The cause is one line of reasonable behaviour: our page generator refuses to overwrite a file a human wrote. That refusal is correct. Its side effect is that the one page type written by a human is the one page type the head template cannot reach.
Nobody found this by reading a page. It was found by a build check that had never existed before, and it had already happened three times.
Our site is static. A Node script reads a registry of games and writes every crawlable page: game pages, category pages, the submission page, the challenge page, the blog index. Every one of those goes through a single function that builds the <head> — title, description, canonical, Open Graph, favicon, fonts, and the analytics snippet.
Blog posts do not go through it. They are hand-written HTML, and the generator carries an explicit instruction not to touch them, sitting right above the list of posts:
// Posts are listed by hand here so the generator never overwrites // the hand-authored post HTML. const BLOG_POSTS = [ ... ]
That comment is not a mistake. Regenerating a hand-written article would destroy the article. But read it as a sentence about analytics and it says something else: the head template cannot reach these files. Anything the template guarantees — the tag, and anything else you later add to it — stops at the boundary.
So the tag arrived by hand, thirteen times, and then did not arrive three times. There was no moment of carelessness that anyone could point at. The mechanism simply had no floor under it.
We counted, because the ratio turned out to be the whole story. Of 235 pages this site ships:
| How the page is produced | Pages | Where the tag comes from |
|---|---|---|
| Built by the generator from the head template | 215 | The template. Cannot be forgotten. |
| Copied verbatim from a hand-maintained folder | 3 | A person, once, in the source file. |
| Mirrored from the hand-edited homepage | 1 | A person, once, in that file. |
| Hand-authored blog posts | 16 | A person, every single time. |
The 215 are safe by construction. The 4 in the middle rows are safe in practice, because they are edited rarely and the tag is already sitting in them.
The 16 in the last row are the exposure, and it is not a coincidence that the failures were all there. It is the only row where the human step repeats. A rule that has to be re-obeyed on every new file will eventually not be, and the number of chances is the number of files you plan to write.
Worth naming the general shape, because almost every static site has all three: generated pages, copied pages, and hand-written pages. Whatever you believe is on every page of your site is only guaranteed on the first kind.
The check is trivial. Search your built output for your measurement ID and list every file that does not contain it. Ours reported three:
3 page(s) missing the analytics tag:
blog/the-same-url-served-two-different-images/index.html
blog/the-star-rating-your-iframe-added/index.html
blog/the-game-metadata-field-nobody-fills-in/index.htmlTwo things make the difference between that check being useful and it being theatre.
Our output directory holds 347 HTML files. Only 235 of them ship. The other 112 are directories for retired pages the generator has never deleted, plus one snippet file that is not a page at all — it is a block of HTML meant to be pasted into another document.
Run the audit over the folder and 112 of your results describe files no visitor can reach. Worse, the noise runs both ways: a retired page that does carry the tag inflates your pass rate, and a snippet that legitimately has no <head> reports as a failure forever. Build the list of pages you actually ship, and audit that.
A check that has never failed is not a check — you have no evidence it can detect anything. So before trusting ours, we deleted the analytics snippet out of one post and re-ran the build:
md5 before strip : 187fbf210f520e496e97f082c41cf25b
tag occurrences : 0 <- deliberately broken
build : "inserted the analytics tag into
hand-authored /blog/the-star-rating-your-iframe-added/"
md5 after repair : 187fbf210f520e496e97f082c41cf25b <- identicalThat single test proves three separate things at once: the check notices, the repair works, and the repair is byte-for-byte non-destructive. A hand-written article is exactly the kind of file where "it probably re-wrote it correctly" is not good enough.
The tempting fix is a note in the authoring instructions: remember to paste the snippet. That is what we effectively had, and it produced thirteen successes and three silent failures.
Instead, the generator now repairs the gap itself. After it writes the blog index, it walks the list of posts and, for any post whose file does not already contain the measurement ID, inserts the same snippet every generated page uses, immediately before the closing </head>. Four properties made it safe enough to run unattended on hand-written prose:
The same pattern already existed elsewhere in this generator, for a different hand-edited file that had gone stale nine times in five weeks. That is the tell: when the second instance of a class of bug appears, the fix is a step in the build, not a better memory.
An awkward detail, recorded because it is the reason this cost nothing. The same three posts were also the three not yet deployed — they return 404 on the live site right now, verified against a deliberate nonsense URL that returned an identical 404 body. So no real visit went unmeasured.
That overlap is luck. The two failures are unrelated and they behave in opposite ways:
| Unpublished page | Untracked page | |
|---|---|---|
| What it returns | 404 | 200 |
| How it looks | Obviously broken | Perfect |
| How you find out | Immediately | Weeks later, in a report |
| What it looks like when you do | A missing page | A page nobody visited |
That last cell is the expensive one. An untracked page does not report as an error, it reports as indifference — and a page that appears to have no audience is a page someone eventually decides not to write again.
Both of these happened during the repair above, both are on Windows, and both are reproducible in three lines. They are here because each one is the same species of problem as the bug itself: a thing that quietly does not do what it appears to do.
Opening a file in Python's default text mode, reading it, and writing the identical string back is not a no-op on Windows. Line endings are translated on write. A three-line file with Unix endings, read and written unchanged, gained three bytes:
printf 'alpha\nbeta\ngamma\n' > lf-control.txt
python -c "d=open('lf-control.txt',encoding='utf-8').read();
open('lf-control.txt','w',encoding='utf-8').write(d)"
carriage returns before : 0
carriage returns after : 3On our 253-line article that turned into 253 changed bytes across the whole file, which is why the byte-for-byte check above initially failed. It was not the repair that changed the file; it was the tool used to inspect it. Open in binary mode, or pass newline='', when the bytes are the thing you are checking.
Worse, the obvious way to check for the damage said there was none. Searching the file for a carriage return reported 0 — on a file that provably contained three:
printf 'alpha\r\nbeta\r\ngamma\r\n' > crlf-control.txt od -c crlf-control.txt | grep -o '\r' | wc -l -> 3 (they are there) grep -c $'\r' crlf-control.txt -> 0 (says clean) grep -c $'\r' lf-control.txt -> 0 (also says clean) grep -cU $'\r' crlf-control.txt -> 3 (correct)
Without -U, the file is treated as text and the carriage return is stripped before the pattern is applied, so the search can never match. Note the third line: the probe returns 0 on a file that has them and 0 on a file that does not. It is not inaccurate, it is inert — there is no input that would make it say anything else.
That is the general rule worth taking away, and it is the same rule that found the original bug. Run your checker against an input you know should fail. If it passes that too, the checker is not measuring anything, and every green result you have collected from it means nothing.
Stated plainly, because a post that only reports its wins is not worth much:
SlowDen is a place to put a browser game where people can find it and play it — no download for the player, no store page for you. Games here come from creators, partners and third-party sources, and each one is credited to whoever made it.
Slow Cook is our creative challenge round. You get a prompt, you build something around it, you submit it for consideration. Selected entries have a chance to be featured, and selected winners may receive a shoutout across SlowDen’s social accounts. That is the honest list; there is nothing guaranteed beyond it. Check the Slow Cook page for the current round and its closing date.
Almost always because those pages did not come out of the function that writes your head element. On a static site there are usually three kinds of page: pages the generator builds from a template, pages the generator copies verbatim from somewhere else, and pages a human wrote by hand. Only the first kind is protected by the template. We counted ours: 215 of 235 shipping pages are built by the template, and the remaining 20 depend on a person remembering. Three of those 20 were missing the tag.
Grep the built output for your measurement ID and list the files that do not contain it, then check that list against the set of files you actually ship. Two cautions from doing it on our own site. First, run the check against your ship list, not your output directory: ours holds 347 HTML files of which only 235 ship, so an audit of the folder measures 112 files that no visitor can reach. Second, break the check on purpose before you trust it. We deleted the tag from one post, re-ran the build, and confirmed the check failed and the repair restored the file byte for byte.
A build step. Thirteen of our sixteen posts carried the tag purely because a person remembered thirteen times in a row, which is a streak rather than a fix, and the streak broke three times without anyone noticing. We replaced it with a step in the generator that inserts the tag into any hand-authored post that lacks it, immediately before the closing head tag. It is insert-only, it never rewrites the author's prose, and a post that already has the tag is not touched at all.
Because some pages are genuinely written by hand and regenerating them would destroy the work. Our blog posts are hand-authored HTML and the generator is explicitly told not to touch them, which is correct. The side effect is that the one page type written by a human is the one page type the head template cannot reach. The refusal is right; what was missing was a step that adds back the few things every page must carry regardless of who wrote it.
No, and that is the whole difficulty. The page renders correctly, validates, carries its canonical link and its structured data, and returns a normal 200. Nothing about looking at it reveals the gap. It surfaces only as an absence in a report you may not read for weeks, by which point the page looks like a page nobody visited rather than a page nobody measured.
An unpublished page returns 404 and you find out immediately. An untracked page returns 200 and looks perfect. In our case the same three posts happened to be both, because they were the three most recent and had not been deployed yet, which is the only reason the missing tag cost nothing. That overlap was luck, not design, and it is worth separating the two failures rather than treating one as evidence about the other.