- Sep 9, 2026
- 5 min read
A Failed Check Is Not a Bad Score
The first time I ran the deep report on my laptop, the headline read “Clear wins for broken links and search visibility”. Then I scrolled down to the priorities, and there was no broken-links priority. No broken-links table either. The report accused the site of something it couldn’t back up.
The site wasn’t the problem. My laptop didn’t have the link checker installed.
How a missing tool became a verdict
The audit runs its phases in parallel and then combines them into one score with fixed weights: performance 40, SEO 20, links 20, security 20 (how the score is computed). The orchestrator starts every category at zero:
# Initialize scores to 0
PERF_SCORE=0
SEO_SCORE=0
LINK_SCORE=0
SEC_SCORE=0
When the link phase runs, it overwrites LINK_SCORE from its summary file,
which records status: complete | tool_error | incomplete_timeout. But with
the tool missing, the link script exits before writing any summary at all:
if ! command -v linkchecker &> /dev/null; then
echo -e "${RED}Error: linkchecker not found!${NC}"
...
So LINK_SCORE stayed at its initial 0, its weight stayed at 20, and the
report generator did exactly what I’d told it to: name the two weakest
categories in the headline. “Links: 0” was the weakest. Nobody had measured
a single link.
It wasn’t even the first time this shape of bug had shown up. Earlier, when
the link check was skipped on purpose (it’s off by default in the quick
audit), the category was correctly weighted zero in the overall score, but
the per-category breakdown still went out to my lead hub as links: 0.
Anyone reading that record later would see a site with terrible links.
Four states, not two
A number can only say “good” or “bad”. A check can end in four ways:
| State | Example | Shown as | Moves the score? |
|---|---|---|---|
| Measured, good | 0 broken links found | 100 | Yes |
| Measured, bad | 13 broken links found | 40 + the broken links listed | Yes |
| Skipped on purpose | Link check not requested | — not checked | No (weight 0) |
| Failed or unknown | Tool missing, no summary, tool error | — not checked | No (overall recomputed) |
Only the first two are facts about the site. The last two are facts about my tooling, and they don’t belong in a grade I’m about to show a business owner.
flowchart TD
A[Category phase finished] --> B{Requested?}
B -- No --> S[Skipped: drop from breakdown, weight 0]
B -- Yes --> C{Summary written?}
C -- No --> U[Failed: drop, recompute overall]
C -- Yes --> D{status = tool_error?}
D -- Yes --> U
D -- No --> M[Measured: score counts]
S --> R[Report tile shows a dash and not checked]
U --> R
M --> G[Report tile shows the score]
The fix
All results go through one function now, shared by the quick audit and the deep report. Skipped categories are removed from the breakdown, not zeroed. A link check that failed, or was meant to run but left no summary, gets the same treatment, and the overall score is recomputed with the same weights the shell uses:
if (links?.status === 'tool_error' || (skipped.skip_links !== true && !links)) {
delete breakdown.links;
links = { status: 'tool_error', broken: [] };
const weights = { performance: 40, seo: 20, links: 20, security: 20 };
const parts = Object.entries(breakdown).filter(([k, v]) => weights[k] && typeof v === 'number');
const total = parts.reduce((t, [k]) => t + weights[k], 0);
if (total) reportData.overall_score = Math.round(parts.reduce((t, [k, v]) => t + v * weights[k], 0) / total);
}
The report renders any category missing from the breakdown as a muted tile: a dash and “not checked”. The same rerun of that example site went from 54 with a “broken links” headline to 68, headlined by the two things that were actually measured as weak.
The same rule, elsewhere
I’d already applied this principle once without naming it. The HTTPS check
probes whether http:// redirects to https://, and a timeout there is
recorded as unknown, never as “no redirect”
(checking HTTPS like a visitor).
Only a plain-HTTP answer that clearly doesn’t redirect costs points.
The reasoning is the same in both places. The audit exists to show someone a real problem on their site. A missed point makes the score slightly generous. A false accusation makes the whole report untrustworthy, and the owner will remember the one line that was wrong long after they’ve forgotten the ten that were right.
Takeaways
- Treat “couldn’t measure” as its own state. Start values of
0quietly turn it into “measured as terrible”. - Skipped and failed categories should be absent, not zero, everywhere the data travels: the overall score, the breakdown, stored records and the rendered report.
- Test the failure paths of your measuring tools — tool missing, timeout, empty output — not only the happy path. My bug only appeared on a machine that was missing one binary.
- The honest report beats the impressive one. If you want to see how yours reads, run your site through the free audit; anything it couldn’t check shows up as exactly that.
Related: the emailed full report and how the HTML report is generated without AI.