SEO Tool False Positives: How to Tell Whether a Tool Measured Your Site or Just Failed to Reach It
Learn to spot SEO tool false positives caused by failed fetches, with a repeatable test, reason codes, and a documented PR #659 case study.
Article highlights
- Estimated reading time: 13 minutes
- Published on: August 10, 2026
- Last updated: August 10, 2026
Article
Author: Ravi Yadav
What we measured
Between 2026-07-25 and 2026-07-31, we tracked one class of defect across two events in our own codebase: six inference errors introduced and fixed inside a single pull request (PR #659) before any production deploy, and one recurrence of the identical error pattern that reached production and stayed there from 2026-07-26 until 2026-07-31. The headline finding: code written to distinguish "target unreachable" from "target reached, check failed" was instead reporting both as the same outcome, twice, in two different places. This piece defines the three-state model that closes the class of bug, gives you a six-step test to run against any SEO tool, and reports the honest result of running that test against our own history rather than a competitor's.
Introduction: a failed fetch is not a finding
"Your site failed this check" and "we could not check your site" are two different facts, and most automated SEO tools do not tell you which one you're looking at. When a scanner cannot reach a target — DNS fails to resolve, the TLS handshake times out, the origin returns a 5xx — the only honest output is unknown. Reporting blocked or missing instead is not caution, it's a guess dressed as a measurement. This distinction matters for why proof systems need current-state truth before they get used to make claims about a site.
The rest of this article walks through a worked example — PR #659, merged 2026-07-25 — where we found six instances of exactly this error in one branch, states the three-state rule that should have prevented it, and then reports that the same error recurred on a surface the rule was supposed to cover. We are not going to soften that second part.
The two-state trap in automated SEO tools
Most audit tools collapse three real outcomes into a binary pass or fail. The three real outcomes are: reached and passed, reached and failed, and never reached. When a scanner never reaches a target, the code path frequently defaults to whichever binary state is easiest to render, and the user reads that default as a confirmed result.
This produces false confidence in a specific, predictable way. An unreachable site gets read as "confirmed absent" (a missing llms.txt, a missing schema property) or "confirmed blocked" (a robots.txt disallow that was never actually fetched), rather than "not observed." We wrote about this failure mode generally in why estimated SEO scores can mislead you; PR #659 is the specific, dated instance of it inside our own product.
The downstream harm is concrete. A practitioner can spend hours removing a robots.txt block that a fetch failure invented, or — worse — miss a genuine block because a later "clean" run was itself just another failed fetch that happened to render as a pass. Either error is expensive, and the tool gives no signal that it has occurred.
Worked example: six instances of the same inference error in one branch (PR #659, merged 2026-07-25)
What we found, and where it was caught
All six instances of this defect lived only on the pull-request branch and were fixed before that code's first production deploy. The branch was squash-merged on 2026-07-25, so none of the intermediate buggy states ever existed on the main branch, and the first production deploy carrying this work, on 2026-07-26 UTC, already contained the fixes. We can verifiably say these six defects never reached production. We cannot verifiably say, and do not say anywhere in this piece, that no customer has ever seen a failed fetch reported as a finding — that claim is not something this record supports, and the next section explains why.
The defects were found by reading the failure paths during code review, not by running a staged outage or taking a test site offline on purpose. There was no experimental rig involved; the six call sites were identified by tracing what each one did when a fetch returned no response at all.
What broke
Across the six call sites, the scanner treated "no response received" as equivalent to "a response was received and the check failed." Concretely:
- The scanner emitted ten confident
CDN-or-WAF-blockingfindings for a site that was simply offline, with zero actual HTTP responses received during the run. - The reach matrix would have rendered
10/10 reachablefor a target that never responded once. - The
llms_txt_missingcheck fired based on a fetch failure, not on an actual retrieved and empty response body. - A stale sweep run under the old logic would have closed a genuine, previously-recorded
robots.txtblock based on a run that observed nothing at all.
Why it happened
Six independent call sites made the identical inference error, which is itself the notable part: this was not one bad function reused six times, it was six separate implementations converging on the same mistake. That convergence happens because unreachable and blocked look identical downstream once the reason code is discarded — both end up as "we don't have the thing we expected," and it's easy to write the next line of code as if that were one state instead of two.
The honest part: the rule did not hold the first time
This section is required, and we are not cutting it or softening it. The same inference error came back on the same surface, in the reach matrix, after the three-state rule below had already been written to prevent it. Between 2026-07-26 and 2026-07-31, the reach matrix printed not found for both a genuine 404 and a failed fetch (403, 429, 5xx, or timeout) — the exact ambiguity the rule exists to remove. The defect was live in production from 2026-07-26 until it was fixed on 2026-07-31.
We have not verified whether any site other than our own encountered this during that window. We do not claim that one did, and we do not claim that none did — we did not check, and checking would require pulling server access logs and reachability-check output for every property scanned in that window and cross-referencing timestamps, which we have not done. The readiness check that carries this logic is available on every TrustGrowth plan tier and runs whenever a site owner triggers it, so there was no structural boundary — no internal-only flag, no staging gate — that by design confined this defect to our own properties.
The real lesson is that "three states, never two" is not a fix you apply once. It is a property every rendering surface has to be re-checked against independently, because a correct backend state can still be flattened back to two states in a display layer, a cache, or a downstream aggregation. Our own running count of this defect class across the reachability feature is higher than the six instances found in PR #659 alone. The work on this is not finished, and this piece is not going to claim otherwise.
The fix: three states, never two
The corrected model uses three states instead of two, and the ordering between them is the part that matters.
State When it applies Required attributeunknown
Fetch never received a response
Machine-readable reason code: timeout, dns_failure, tls_handshake_failure, connection_refused, http_5xx
blocked
Directive parsed from a successfully-fetched robots.txt file, per the Robots Exclusion Protocol (RFC 9309)
Must never be inferred from a failed connection
partial
Any run containing at least one failed live fetch alongside successful ones
Must not silently overwrite prior known-good findings
Source: TrustGrowth internal error-state taxonomy, effective from PR #659, 2026-07-25.
The ordering prevents two distinct failure modes at once. It prevents false negatives, where a real robots.txt block gets erased because a later run failed to fetch anything and was misread as a clean pass. It also prevents false positives, where a site that is simply down gets diagnosed as actively blocking crawlers. These claim-safety rules aren't limited to this one check — they're part of the claim-safety rules behind every score we publish.
A repeatable test: how to check whether any SEO tool actually reached your site
You can run this test against any audit tool, including ours, without needing access to its source code.
- Take a test path offline or firewall it temporarily, or point the tool at a known-dead subdomain you control.
- Run the tool being evaluated and record every finding it returns for that path, verbatim.
- Pull server access logs for the same window and check for the tool's user-agent string and request timestamps.
- Compare: did the tool report an explicit
unknownor error state with a reason code, or did it report a substantive pass, fail, or blocked finding? - Check any aggregate reachability metric the tool shows (for example, an "X/X reachable" summary) against what the access logs actually confirm.
- Flag any tool that reports zero
unknownresults across a large page set — that absence is itself a signal the failure path may not exist in that tool's codebase.
A trustworthy tool's output on this test looks like an explicit unknown: dns_failure with a timestamp and a partial-run label on the rest of the report. A guessing tool's output looks like a confident blocked or missing finding with no reason code attached and no acknowledgment that the run was incomplete. You can see the shape of this test in our own free AI crawler access check, but run step 3 against your access logs rather than trusting its output — because that tool currently has the defect this article describes. At the service layer its homepage fetch is honest: a domain that never resolves returns an explicit could-not-check state rather than a finding. The failure is one layer in, on the robots.txt request behind it. Until 2026-08-08 that fetch did not treat an HTTP status code as an error, so a WAF challenge or a 403 or 5xx page returning an HTML body was accepted as a retrieved robots.txt: the panel printed Found at the top while every tracked crawler row underneath read no robots txt, one panel asserting both that the file was retrieved and that it does not exist. That half was fixed on 2026-08-08 — the fetch layer now rejects any non-2xx response. The step-4 failure did not go with it. A rejected robots.txt fetch is still recorded as a plain absence, indistinguishable from a genuine 404, so a site whose robots.txt sits behind a 403 now reads as "no robots.txt was found, so crawler access defaults to open" — a confident all-clear, with no reason code, assembled from a request that was never answered. The symptom moved and the inference did not, which is the same lesson the reach matrix taught above: repairing the fetch does not repair the surface that renders it.
One note on how we know that, because this article's own standard applies to this article. That paragraph was sourced by reading the tool's service code and result template on the deployed build, re-checked 2026-08-09, not by running the six-step test against it — the public form sits behind a bot challenge, and we did not script a run around it. It is a claim about code we can read, not about an output we observed.
Reading reason codes: what unknown should tell you
A bare blocked or missing finding with no attached reason code is a red flag, not a result — treat it as unverified until you can confirm the fetch actually completed. Each reason code implies a different next action for you as the practitioner.
timeout
Server accepted the connection but did not respond in time
Check origin response time under load, not robots.txt content
dns_failure
The hostname did not resolve for the scanner
Verify DNS propagation and the scanner's resolver, not crawl directives
connection_refused
The port was reachable but nothing was listening
Check firewall rules and service status, not indexing settings
tls_handshake_failure
Certificate or protocol mismatch stopped the connection before HTTP
Check certificate chain and TLS version support
http_5xx
Server responded but with a server-side error
Check application logs for the same timestamp window
Source: TrustGrowth internal error taxonomy, referenced in how our free audit checks 80 data points.
None of these five codes should ever be silently converted into blocked or missing further down the pipeline. If a tool's report shows one of these findings without the underlying reason code, the finding was generated after that conversion already happened, and you have no way to tell which of the five actually occurred.
What this changes about how you read any audit report
Don't average unknown results into a pass rate — an unknown is not a fractional pass, it's an absence of data, and folding it into an average manufactures a number the underlying crawl never produced. Don't let a single failed crawl overwrite a previously-verified finding; a robots.txt block confirmed on 2026-07-20 does not stop being true because a run on 2026-07-27 timed out. Ask any tool vendor directly whether their reachability metric is computed per-run or carried over from a stale sweep, because the answer changes what "10/10 reachable" actually means.
This ties back to a broader requirement for any scoring system that claims to represent current state: the score has to reflect what was actually observed in the most recent run, not what was last successfully observed at some unspecified earlier point. We make this argument in more general terms in why proof systems need current-state truth, and PR #659 plus its recurrence is the specific evidence behind that argument, not a hypothetical.
The honest closing position is not that this is fixed. It's that this class of defect is still being found inside our own reachability feature — including in the free crawler check linked above — and the right response as a reader is to hold every tool — including TrustGrowth — to the log-comparison test in the section above, rather than to a vendor's assurance that it doesn't happen. You can see the kind of verified, dated evidence this standard produces on our public, inspectable proof pages, and see what a GSC-verified audit looks like when it's anchored to Search Console data instead of an unverified crawl.
Method
- Data source: PR #659 diff and commit history; production deploy log for 2026-07-26; incident fix record for 2026-07-31.
- Sample: six call sites within one pull request branch, plus one recurrence instance on the reach-matrix rendering surface.
- Date range: 2026-07-25 (PR merge) through 2026-07-31 (recurrence fix).
- Exclusions: this record does not include any external site's crawl data; we did not query other customers' scan histories for this analysis.
- Tooling: manual code review of failure-path branches; no staged outage or offline test environment was used to surface the original six defects.
Limitations
This record covers only our own codebase and does not constitute a survey of other SEO tools' handling of unreachable targets; readers should apply the six-step test above to any specific vendor rather than generalize from this one case. We have not verified whether any site other than our own was affected during the recurrence window (2026-07-26 to 2026-07-31), and we have not attempted to reconstruct that verification retroactively, so the true scope of the recurrence's external impact, if any, is unknown to us.
FAQ
What is an SEO tool false positive?
An SEO tool false positive is a finding — such as a robots.txt block or a missing file — that the tool reports with confidence even though it never successfully fetched the resource needed to confirm it. The correct output in that situation is an unknown state with a reason code, not a substantive finding.
How do I know if an SEO audit tool actually crawled my site?
Compare the tool's reported findings against your own server access logs for the same time window, checking for the tool's user-agent string and request timestamps. If the tool reports a blocked or missing finding for a path your logs show it never successfully requested, that finding is unreliable.
What does a reachability reason code like dns_failure mean?
It means the scanner's DNS resolver could not resolve your hostname during that specific run, and the fetch never reached your server at all. It does not mean your site is blocking crawlers, and it should never be reported as a robots.txt block.
Why did TrustGrowth's own reach matrix show the same bug twice?
The first six instances were caught in code review before a production deploy on 2026-07-26. A related instance recurred on the reach-matrix display surface itself and was live in production from 2026-07-26 to 2026-07-31, showing that fixing the underlying logic once does not guarantee every rendering surface inherits the fix.
Should I trust a tool that never reports unknown results?
Treat it with caution. A large-scale crawl that reports zero unknown or error states across a big page set is itself a signal that the tool's failure-handling path may be missing or discarding those states rather than genuinely never encountering a failed fetch.
Key takeaways
- Three real outcomes exist for any crawl check: reached-and-passed, reached-and-failed, and never-reached. Tools that render only two states are guessing on the third.
- PR #659 (merged 2026-07-25) contained six independent instances of this error, all caught and fixed before the 2026-07-26 production deploy.
- The identical error pattern recurred on the reach-matrix display surface from 2026-07-26 to 2026-07-31, proving the fix needs re-verification per rendering surface, not just per backend function.
- The three-state model requires
unknown(with a reason code),blocked(only from a successfully-fetchedrobots.txt), andpartial(never silently overwriting prior known-good findings). - Run the six-step reachability test in this article against any SEO tool, including TrustGrowth, before trusting its
blockedormissingfindings. Our own free crawler access check is currently one of the tools that fails step 4.
Know your site's real SEO score
Free GSC-verified audit, E-E-A-T scoring, and AI-powered content strategy.
Get Started Free