Skip to content
Go back

Sending A Squad After One Bug: One Hypothesis Per Agent 🔦

Every build I ran for weeks ended with this:

1 page found without an <html> element.
Pages without an outer <html> element will not be processed by default.
If adding this element is not possible, use the root selector config to target a different root element.
  * "/archives/" has no <html> element

And /archives/ rendered perfectly. Click it, wait a beat, it does what it should. Nothing was broken, nothing looked broken, and the build kept calmly telling me one of my pages had no <html> element — which, for a page, is not a small claim.

I stared at that for longer than I’d like to admit, and the reason it took so long is the reason this post exists: every explanation was plausible, and none of them were checkable from where I was sitting.


Why One Brain Stalls 🧊

The four candidates I could see:

  1. Astro stopped emitting a proper document shell for that route.
  2. pagefind changed how it parses and is now wrong about this.
  3. The template for that page is malformed.
  4. The file in dist/ isn’t actually the page I think it is.

Each one points at a completely different part of the stack. Chasing them in sequence means four context switches, and — this is the part that always catches me — the first one you check usually feels the most likely simply because you checked it first.

That’s not debugging. That’s a queue with a bias.

So instead of asking one agent “why is this happening”, I sent a squad out after a single bug.

Diagram: one build warning fans out into four parallel branches, each agent given exactly one hypothesis to disprove — Astro, pagefind, the template, or the built file. The branches reconverge into a single verdict: the file is a 288-byte redirect stub with no html element. Only one branch's evidence survives to the merge.

One Hypothesis Per Agent 🎯

The rule that makes this work at all:

Every branch gets exactly one sentence to disprove. Not “look into the archives page.” Not “figure out this warning.” One claim, yes or no, with the command you ran.

“Look into it” produces paragraphs. A single falsifiable claim produces an answer.

So the four branches were:

#The claim it had to disprove
1Astro emits a full document shell for every route in dist/.
2Every file pagefind indexes has an outer <html> element.
3dist/archives/index.html is a rendered page.
4The warning is new — it wasn’t in builds from before the upgrade.

Notice what branch 4 is doing. It isn’t explaining anything. It’s checking when the thing started, which is the single cheapest way to kill a wrong theory: if the warning predates the change you suspect, that theory is dead and you’ve just saved an hour.

Three of the four came back with the same shape of answer: here’s the command, here’s the output, here’s what it doesn’t say. Useful, and none of them decisive.

The fourth one wasn’t a theory at all. It was the trap.


The Answer Was In The File System 📁

Branch 3 didn’t need a clever theory. It needed ls -l:

$ ls -l dist/archives/index.html
-rw-r--r--. 1 bitzy bitzy 288 Oct  4 04:18 dist/archives/index.html

Two hundred and eighty-eight bytes.

Real pages in this build run to tens of kilobytes. A document with a <body>, a header, an article and a footer does not fit in 288 bytes.

$ head -c 288 dist/archives/index.html
<!doctype html><title>Redirecting to: /404</title><meta http-equiv="refresh"
content="2;url=/404"><meta name="robots" content="noindex"><link rel="canonical"
href="https://ownyourstack.dev/404"><body>	<a href="/404">Redirecting from
<code>/archives/</code> to <code>/404</code></a></body>

Read that opening tag list: <!doctype html>, <title>, <meta>, <link>, <body>.

There is no <html> element. And there’s no mistake about it, either — the file has no <html> opening tag because it was never meant to be a page. It’s a redirect stub that a router emits when there’s nowhere for that URL to go.

pagefind was right. It had been right the whole time. It found a file, checked for a document element, didn’t find one, and said so, politely, on every single build while I went looking for a sophisticated cause.


Which Theory Died 🪦

Back to those four branches:

#ClaimVerdict
1Astro drops the document shell✗ Full shells everywhere else in dist/
2pagefind now misparses✗ It correctly found a file with no <html>
3The built file is a real page✗ 288 bytes, no <html> — it’s a redirect
4The warning is new✗ Pre-dates the upgrade entirely

Branch 4’s answer was the one I’d have built my whole investigation on if I’d gone in sequence: no, this was here before too. Everything I suspected about a recent dependency bump was dead in a single sentence, and I’d never have asked that question first on my own.

Branch 3 was the answer. The other three weren’t wasted — they were the absence of alternatives, which is the part people skip and then wonder why the conclusion felt lucky.


What I Actually Changed 🔧

Nothing. That’s the honest ending.

The warning is correct and harmless. Somewhere in the config the archives route is switched off:

// src/config.ts
showArchives: false,

So src/pages/archives/index.astro redirects to the 404 page, the redirect stub gets written, pagefind finds it, and pagefind correctly declines to index something that isn’t a document.

The warning is the system doing its job loudly about a thing I deliberately did. The right fix would be to make the warning go quiet — excluding that path from the index — not to “repair” a redirect that’s working exactly as designed.

Knowing that is why it was worth four branches instead of one frustrated hour.


The Protocol I Keep Reusing 📜

  1. Write the claim before you write the prompt. If it can’t be disproven, it isn’t a hypothesis — it’s a mood.
  2. Never give two branches the same claim. They’ll both confirm your existing suspicion and you’ll call it a consensus.
  3. Force every branch to return a command, not a conclusion. A conclusion can be argued with. An output can’t.
  4. Spend one branch on when, not why. “Has this always been true?” kills more wrong theories than any amount of reasoning.
  5. Merge one. The point of sending a squad is not to get four opinions — it’s to get one answer that survived three competitors.

That last one is the whole discipline. Fan out wide, merge narrow. One bug, four questions, one survivor. 🔦

Have you ever had a tool quietly telling you the truth for weeks while you went hunting for a subtler cause? I’d love to hear it. 👇


Share this post on:

Previous Post
Three Codex Threads, Three Command Names: Keeping Parallel Agents From Talking Over Each Other 🧵
Next Post
The 2AM Plugin Autopsy: Two Lines Of Config That Never Ran 🩺