What Check Engine got wrong, and when it was fixed
An audit tool that is wrong sends you after a problem you do not have. This page lists 46 releases of Check Engine, from 23 September 2026 to 5 November 2026, that fixed something it got wrong. Each entry names the release and the day it was published, says what was wrong in plain words, how we found it, what changed, and which tests now hold the fix in place. Where we could not prove something, the entry says so.
This is the part of our log that has shipped as a release. It is not a list of every mistake, only of the ones we found and fixed. For the questions to ask of any tool's report, ours included, see how to tell whether an audit tool is lying to you; it counts 198 of Check Engine's 693 test files that record a past mistake in their own words. After the releases, 15 worked cases show what such mistakes look like from the reader's side.
How to check an entry
The release link under each heading goes to the public release on GitHub, which shows the day it was published. The test files are the 106 files in Check Engine's own suite that pin these fixes; their names are given so you can see what each one is about. Speed-ups and new features are not listed, except where the slow path was leaving results out.
A demo was called placeholder text, and rated products in an article were asked for images
Release 1.3.150, published 5 November 2026. The release on GitHub.
What was wrong. On a software company's site, a card offering "a sample private podcast" to try was reported at high severity as unfinished template copy, products rated in a review article were reported as incomplete markup for lacking an image Google does not require there, and one topic word on two thirds of the pages was reported as no consistent message. An empty schema value was described as missing, and an area with no readable page said "nothing was crawled" or "not applicable".
How we found it. Check Engine's own testing, reading every finding of an audit of a real software company's site against the pages, and checking the previous release on the live download.
What changed. "This is a sample" counts only as a template's own filler. A rated product on a page that declares itself an article is not asked for an image. A word on at least half the pages counts as a shared message. Empty values are called empty, and unread areas say no page could be read.
Tests that hold it. test_a_sample_the_site_offers_is_not_placeholder_text.py, test_a_rated_product_in_a_review_article_needs_no_image.py, test_one_word_on_most_pages_is_a_consistent_message.py, test_an_empty_property_is_called_empty.py, test_a_page_that_answered_500_was_crawled.py
Notes were counted as problems, and error pages were called refusals
Release 1.3.149, published 5 November 2026. The release on GitHub.
What was wrong. Findings that say "No action needed" were counted as problems in every count a reader sees: per area, in "Check Engine found N more problems" and in the report's "issues found", so an offline run, which does less, showed more. A site whose pages all answered with a server error, "not found" or JSON was told every page refused Check Engine access, and the free version's one problem in full was an unrelated HTTPS finding. On a site whose content is built by script, unread areas said "not applicable" beside a plain score. A redirect used up two places in the page limit, a site on one port could be crawled through another port on the same host, and reports said Core Web Vitals were not measured when Google's field data had been read.
How we found it. An independent full audit of the product and this site, ordered by the owner, ran each case against local test sites.
What changed. Counts of problems leave notes out, and notes keep their own label. The verdict names what came back, and the free version shows the problem that explains it. Unread areas say "not assessed" and the score says when it is not a verdict on the site. The page limit counts pages kept, the port is part of what makes a site, and reports say what this run found about Core Web Vitals.
Tests that hold it. test_a_notice_is_not_a_problem.py, test_an_error_homepage_is_not_a_refusal.py, test_the_page_budget_counts_pages_not_requests.py, test_another_port_on_the_same_host_is_another_site.py, test_a_report_that_read_core_web_vitals_says_so.py, test_a_log_list_says_how_many_it_left_out.py, test_no_two_checks_share_a_title.py, test_a_free_copy_is_told_only_activation_keeps_audits.py
A site that blocked Google was told to unblock AI crawlers first, and software companies were scored as local businesses
Release 1.3.148, published 5 November 2026. The release on GitHub.
What was wrong. Under a robots.txt that blocked every crawler, the top problem, the one the free version shows in full, was "robots.txt blocks 4 AI search crawlers", with a fix that let those in and left Google blocked. Separately, a software company with its office address and phone in the footer had the Local business SEO area switched on and scored, with or without software schema, while the home page said Check Engine knows the difference.
How we found it. An independent full audit of the product and this site, ordered by the owner, ran both cases against local test sites.
What changed. When Google is blocked from the whole site, that problem comes first and the AI crawler problem says it is the same fix. A software site, by its schema or its wording, is not treated as a local business unless something says it serves customers in a place.
Tests that hold it. test_a_site_that_blocks_google_too_is_told_that_first.py, test_a_software_company_with_an_office_is_not_a_local_business.py
The report cover said "1 items"
Release 1.3.147, published 5 November 2026. The release on GitHub.
What was wrong. On a report with one fixable item, the PDF and HTML covers read "Fix everything in this report and this becomes 100/100: 1 items". The app already said "1 item".
How we found it. Rendering the first branded report cover while building report branding.
What changed. Both covers count one item in the singular.
Tests that hold it. test_a_branded_report_carries_the_agency_not_docket.py
A site whose robots.txt shut Check Engine out still had its sitemap, llms.txt and favicon fetched
Release 1.3.146, published 4 November 2026. The release on GitHub.
What was wrong. With robots.txt disallowing everything, to every crawler or to Check Engine by name, Check Engine stopped crawling at the address typed, as this site says, but then requested three sitemap locations, /llms.txt, /favicon.ico, a made-up address and the start page up to three more times. The bot page said the address typed is requested once and nothing beyond it.
How we found it. An independent full audit of the product and this site, ordered by the owner, logged every request an offline audit made of a local site whose robots.txt said Disallow: /.
What changed. Every request an audit makes for itself now asks robots.txt first; only robots.txt and the address typed are exempt. A file it was told not to fetch is not reported missing. The crawler fetches the start page once and reuses that copy.
Tests that hold it. test_robots_txt_governs_every_request_but_the_address_typed.py
The PDF cover showed an old logo, and its severity counts crossed the score panel's edge
Release 1.3.145, published 4 November 2026. The release on GitHub.
What was wrong. Every exported PDF's cover drew an old mark, a line climbing to a flag, in dark teal on the black header, where it read as a faint stroke; the app and this site use a shield with a rising arrow. On a cover whose note about excluded findings ran to three lines, the medium, low and notice counts sat across the score panel's bottom border. Both showed in this site's own sample report and in the home page's picture of it.
How we found it. Looking at the home page's new picture of the sample report's first page, the day it went up.
What changed. The cover draws the app's own shield and arrow, in the app's teal for dark backgrounds. The score panel is measured against everything written in it and grows to hold it.
Tests that hold it. test_the_pdf_cover_draws_the_apps_mark.py, test_the_pdf_score_panel_holds_its_own_text.py
A free result's first Buy box left out the launch price
Release 1.3.144, published 4 November 2026. The release on GitHub.
What was wrong. A free result has two buy boxes. The one right under the top problem, the first a free user meets, quoted $59 once or 4 x $14.75 and nothing else at the $29.50 launch price; only the box further down gave the launch price.
How we found it. Retaking this site's free-result screenshot on 1.3.143: the box in the picture showed the full price alone.
What changed. Both boxes read the same offer and carry the same launch-price line, and both drop it when the launch price ends.
Tests that hold it. test_every_price_on_a_free_result_carries_the_sale.py
The command line scored a mistyped address as a site, and the free version quoted the wrong price
Release 1.3.143, published 4 November 2026. The release on GitHub.
What was wrong. checkengine audit "not a url" crawled nothing, scored the address 0 with every area "not applicable" and exited 0, so a typo in a deploy script looked like a passing run. Without a key, the command line's footer quoted $59 once or 4 x $14.75 at the $29.50 launch price in the app. Licence messages said "This Mac" on Windows too.
How we found it. An independent full audit of the product and this site, ordered by the owner, ran the command line with a malformed address and read its footer against the live offer.
What changed. An address with a space, a malformed port or no host is refused before any crawl, with exit code 1. The free footer reads the same live offer as the app and gives the launch price while it runs. Licence messages say "computer": one licence covers up to 3 computers at a time, Mac or Windows.
Tests that hold it. test_the_cli_quotes_the_sale_says_computers_and_refuses_a_non_address.py
The free command line always reported success, and a free diff said nothing had changed
Release 1.3.142, published 4 November 2026. The release on GitHub.
What was wrong. Without a key, checkengine audit exited 0 on every site, including one whose homepage told search engines not to index it and one that did not answer at all, so a deploy gate built on the free command line could never fail. Comparing a clean site with one carrying dozens of planted problems, a free checkengine diff printed "No change in findings between the two." and passed.
How we found it. An independent full audit of the product and this site, ordered by the owner, ran the free command line against local test sites and checked the exit code of each run.
What changed. The free command line now exits 2 at the --fail-on threshold, as the paid one does, and says the threshold was reached. A free diff says how many problems got worse and how many got better, and fails the build on the real regressions; which ones they are is in the paid version.
Tests that hold it. test_the_free_cli_gates_a_deploy_and_never_says_no_change_falsely.py
Reports listed an audit Check Engine could not judge with an ordinary score
Release 1.3.141, published 4 November 2026. The release on GitHub.
What was wrong. When Check Engine could not read enough of a site to judge it, the report itself said to treat the score as unknown, but the Reports list showed the kept audit with a plain number beside every other audit. Every kept report was also stored with no grade.
How we found it. Building the tools that let an AI assistant read kept audits: the list was fed the score without the two facts that say whether it is a verdict on the site.
What changed. A kept audit now keeps its grade and whether its score speaks for the site, and the Reports list marks it the way the results screen does. Audits kept before this keep their old row.
Tests that hold it. test_a_kept_report_says_when_its_score_is_not_a_verdict.py
A site with no business markup scored 100 for AI search visibility
Release 1.3.140, published 3 November 2026. The release on GitHub.
What was wrong. A six-page property manager's site with no structured data at all scored 100 for AI search visibility, in a report whose own text said structured data is where the gains in AI answers come from. A site that had added business markup but no profile links lost points in the same area, so doing less scored better. The same audit gave Structured data 77 with no structured data on any page.
How we found it. The owner of the report asked how AI visibility could be 100 with no structured data. A bare test site then showed the same shape in four more areas: Social and sharing 94 with no Open Graph tags, Tracking 72.5 with no analytics, and Security 58 with no HTTPS.
What changed. No business markup now costs the AI area as well, as the same job as the Structured data finding. An area whose whole subject is missing holds at 45, in the red, wherever Check Engine can be sure of the absence.
Tests that hold it. test_a_site_with_no_business_markup_is_not_perfectly_visible_to_ai.py, test_a_lane_whose_subject_is_missing_cannot_pass.py
The app quoted the old price after Check Engine became $59
Release 1.3.139, published 2 November 2026. The release on GitHub.
What was wrong. On 2 November Check Engine's price became $59 once. Every copy up to 1.3.138 holds the list price it was built with, and trusts the price the website publishes only when the two agree, so it went on showing the old list price, or four payments of the old amount, beside the Buy button. The checkout charged the new price.
How we found it. The price change itself: the website moved to $59 the same day, and the app's own still of a free audit still printed the old price.
What changed. The app reads the website's current price. From 1 December, the standing price is $59 once, or four payments of $14.75. A price under the checkout's $50.01 minimum for paying in four, such as the $29.50 launch price, is shown with no instalments, even if the website's offer file carries one. The launch price is shown under the name the website gives it, with no limit on buyers.
Tests that hold it. test_a_price_under_the_pay_in_four_floor_shows_no_instalments.py, test_an_uncapped_sale_is_shown_with_its_name.py
A large site that asks crawlers to wait could not be audited whole in a day
Release 1.3.138, published 2 November 2026. The release on GitHub.
What was wrong. Check Engine waits the delay a site's robots.txt asks for between requests. A furniture retailer with about 23,000 pages asking for 4 seconds would have taken about 26 hours, longer than the day an audit may run, so even its owner could not audit the whole site.
How we found it. A whole-site audit of a large furniture retailer, with its robots.txt asking for a 4-second wait, stopped long before the end.
What changed. An owner auditing their own site can now choose not to wait the delay. Every page robots.txt blocks is still left unread, and the report says the delay was skipped and that Google does not read it at all. By default the delay is kept, as before.
Tests that hold it. test_an_owner_can_skip_the_crawl_delay_on_their_own_site.py
A gym marked up as a gym was told it had no local business markup
Release 1.3.137, published 2 November 2026. The release on GitHub.
What was wrong. A CrossFit gym whose homepage marks it up with schema.org's ExerciseGym type, address and phone included, was told at high severity that it had no local business markup. Check Engine recognised a hand-picked list of local business types that left out ExerciseGym and most others. Once the gym was recognised, the same homepage's shorter of two descriptions was checked instead of its full one, and fields the page does publish were called missing. Separately, a food bank's phone links with an extension written as a letter and digits were described as numbers spelled in letters that dial nothing.
How we found it. False-positive hunts on a CrossFit gym's site and a food bank's site, with every finding read against the live pages and the list of types read from schema.org's own vocabulary.
What changed. Check Engine now recognises every local business type in schema.org's vocabulary, less public services and broadcasters, and checks the most complete of a site's own descriptions. A phone link with an extension is described as an extension, with the standard way to write one; a number really spelled in letters keeps its finding.
Tests that hold it. test_a_gym_marked_up_as_a_gym_has_local_markup.py, test_a_phone_extension_is_not_a_vanity_number.py
One address was reported as 18 variations, by a fix two releases earlier
Release 1.3.136, published 1 November 2026. The release on GitHub.
What was wrong. The 1.3.134 change that reads a US address line however it is capitalised started the line at the first number it could find. On a florist whose shop pages show a result count just before the address, a different count on every page, that count was read as part of the address, and one address was reported as 18 address variations.
How we found it. A false-positive hunt on a florist's site the day after 1.3.134 shipped, reading each variation against the page it came from.
What changed. The address line now starts at the last number that can begin a street line, and a number after Suite, Ste, Apt or # stays inside the address rather than starting it. The accounting firm whose address prompted 1.3.134 is still read correctly.
Tests that hold it. test_an_address_in_capitals_is_an_address.py
A winery's reviews page was not counted as showing reviews
Release 1.3.135, published 1 November 2026. The release on GitHub.
What was wrong. A family winery whose reviews page shows quoted reviews signed by its customers was told at medium severity that it had no customer reviews or testimonials on the pages checked, that page among them. An auto repair shop built on Kukui, a website platform for repair shops, showed no platform at all.
How we found it. False-positive hunts on a family winery's site and an auto repair shop's site, with every finding read against the live pages.
What changed. A page named Reviews or Testimonials by its address or its main heading now counts as labelled, and it still has to show a quoted review, a star rating or two signed reviews; a reviews page with nothing on it, and quotes on a page about something else, still do not count. Kukui is recognised from the address its files load from.
Tests that hold it. test_a_page_called_reviews_is_its_own_label.py, test_a_duda_site_is_named_duda.py
An accounting firm was told it was not a local business, and an inn that its titles named no place
Release 1.3.134, published 1 November 2026. The release on GitHub.
What was wrong. An accounting firm whose header gives its full street address, with the town in capital letters, on every page was told it did not look like a local business, so none of the local checks ran for an office its clients visit. A country inn whose every page title names its state, the state of its own address, was told at medium severity that no page title mentioned a location.
How we found it. False-positive hunts on an accounting firm's site and a country inn's site, with every finding read against the live pages.
What changed. A whole US address line ending in a town, a two-letter state code and a ZIP code now counts as an address however it is capitalised, both for deciding that the local checks apply and in the address check itself, without counting one address twice. The state of a site's own address, written as a code before its ZIP or in its markup, now counts as a location in its titles. A town and ZIP with no street, a year and a mailing address still do not count as a place to visit.
Tests that hold it. test_an_address_in_capitals_is_an_address.py, test_a_title_naming_the_state_names_a_location.py
A law firm was told it had no map, stray typefaces and no recognised profiles
Release 1.3.133, published 1 November 2026. The release on GitHub.
What was wrong. A family-law firm's site was told it had no map, though its directions page embeds a Google map that loads as the visitor scrolls. It was told none of its typefaces was used site-wide and that the rest pointed to an old template or an embedded widget, when Check Engine had read the stylesheets of only a few of its pages; Font Awesome was counted as one of the typefaces. Its structured data links its Martindale, Avvo and Lawyers.com profiles, and it was told it linked none Check Engine recognised.
How we found it. A false-positive hunt on a family-law firm's site, with each finding read against the live pages and the site's stylesheets measured by hand.
What changed. A frame whose address waits in a lazy-loading attribute is read the way lazy images already were. Pages whose stylesheets were left unread by Check Engine's size limit are no longer counted, and the finding says how many were left out. Font Awesome is recognised as an icon font under its hyphenated name. Martindale, Avvo and Lawyers.com count as recognised profiles, and a site with none is no longer told it has only zero.
Tests that hold it. test_a_lazy_loaded_map_frame_is_a_map.py, test_typefaces_are_counted_only_where_the_css_was_read.py, test_a_law_firms_directory_profiles_are_recognised.py
Two builder-made sites were told they lacked a postcode, a map and proof they showed, and named the wrong platform
Release 1.3.132, published 1 November 2026. The release on GitHub.
What was wrong. An electrician's site with its full address and postcode in the footer of every page was told no postcode appeared anywhere and to put one in the footer; its contact page map, a builder's map widget, was reported as no map; and its typeface count included three stand-in fonts that are Arial sized to match the real ones. A plumber's homepage saying it is licensed and insured and recognised by the state board that licenses it was told it showed no certifications. Duda sites showed no platform, and GoDaddy sites would have shown the name of a GoDaddy subsidiary on the report cover.
How we found it. False-positive hunts on a plumber's GoDaddy site and an electrician's Duda site, with every finding read against the live pages, and the homepages of four Duda sites and several GoDaddy sites from public lists.
What changed. The postcode check now reads the footer, as Check Engine's other address checks already did. An element named as a map that holds a latitude and a longitude counts as a map. Licence phrases such as licensed and insured count as proof; the word licensed alone does not. A font face whose only source is a font already on the computer is not counted as a typeface. Duda is recognised from its own markup, and GoDaddy's builder is named GoDaddy.
Tests that hold it. test_a_footer_postcode_and_a_map_widget_are_seen.py, test_a_trade_licence_is_proof.py, test_a_metric_fallback_face_is_not_a_typeface.py, test_a_duda_site_is_named_duda.py, test_a_godaddy_site_is_named_godaddy.py
Four corrections from two sites: a robots.txt the owner cannot edit, the wrong platform, reviews not seen, and the wrong cause for broken markup
Release 1.3.131, published 1 November 2026. The release on GitHub.
What was wrong. On a Squarespace site, a finding about AI crawler names their makers no longer use gave a block to paste into robots.txt; Squarespace's own help says its users cannot access or edit that file, and the lines come from Squarespace's setting for blocking AI crawlers. A site built with Webflow was reported as running on WordPress, because its structured data, pasted from a template, pointed at a WordPress-style image folder; a second Webflow site showed no platform at all. The same Webflow site's homepage shows 24 Google reviews under a line counting more than two thousand, and was told at medium severity that it had no customer reviews. Its high-severity structured-data finding said three blocks each held two separate JSON documents; each held one document followed by three closing brackets that closed nothing.
How we found it. False-positive hunts on a photographer's Squarespace portfolio and a dental practice's Webflow site, with every finding read against the live pages, Squarespace's help pages on robots.txt and on asking AI models to exclude a site, and the homepages of three real Webflow sites.
What changed. On Squarespace the robots.txt finding still lists the retired names, and its fix now names the setting and links Squarespace's help page instead of offering markup. Check Engine now reads Webflow's own markup first, and text inside structured data no longer decides the platform. Two or more different first-name-and-initial names near a reviews label now count as reviews, as two dash signatures already did; one such name, or names with no reviews label, still do not. When what is left after a complete block starts with a closing bracket, the finding says the block closes more brackets than it opens and gives the line where the leftover starts; two documents in one block keep the two-documents advice.
Tests that hold it. test_a_squarespace_robots_file_is_not_the_owners_to_edit.py, test_a_webflow_site_is_named_webflow.py, test_reviews_signed_name_first_are_reviews.py, test_leftover_closing_brackets_are_not_a_second_document.py
Check Engine said Google slows down for a site's crawl-delay rule; Google ignores it
Release 1.3.130, published 31 October 2026. The release on GitHub.
What was wrong. When an audit ran out of time on a site whose robots.txt asks for a delay between requests, the report said such a site is read slowly by every crawler, Google included. Google's robots.txt documentation lists crawl-delay among the rules it does not support.
How we found it. A whole-site audit of a large furniture retailer that stopped at the 15-minute limit, read against Google's own documentation.
What changed. The report now says that crawlers which honour the rule, Check Engine among them, read such a site slowly, and that Google does not read it at all. Choosing Everything now allows an audit up to a day, and Finish now keeps the pages already read.
Tests that hold it. test_a_whole_site_audit_is_not_cut_off_at_fifteen_minutes.py
A Squarespace site was told nothing about its visitors was being recorded
Release 1.3.129, published 31 October 2026. The release on GitHub.
What was wrong. A photographer's portfolio on Squarespace had no third-party analytics tag, and Check Engine said at high priority that nothing about visitor numbers, traffic sources, or which pages generate enquiries was being recorded. Squarespace's own Traffic panel counts unique visitors, visits and pageviews on every plan, without a tag.
How we found it. A false-positive hunt on a Squarespace portfolio, reading each finding against the live site and Squarespace's own help article.
What changed. On Squarespace, as already on Wix, the absence of a third-party tag is stated as a notice that says the platform records visits itself, and what a third-party tag adds.
Tests that hold it. test_a_squarespace_site_records_visits_without_a_tag.py
Structured data naming a business after its template's own tag was not reported
Release 1.3.128, published 31 October 2026. The release on GitHub.
What was wrong. When a page's template failed to fill a value inside quotes, such as the business name, the structured data was still valid JSON, so Check Engine said nothing while the markup told every reader the business was called something like {{ studio.name }}.
How we found it. Working through the list of gaps a paying user would notice first, rebuilt from two weeks of audits of real sites.
What changed. A value that is wholly a template tag, or a field that names or locates the business holding one, is reported with the field and the tag. A tag inside running text, such as a code example in an article, is not.
Tests that hold it. test_a_template_tag_in_a_quoted_value_is_not_silent.py
One broken structured-data mistake was reported as two, with advice that pointed elsewhere
Release 1.3.127, published 30 October 2026. The release on GitHub.
What was wrong. When the same JSON-LD mistake appeared on several pages at different places, such as a trailing comma, the report counted it as different kinds of error and said fixing one would not make the pages valid, although one fix covered them all; it then offered a general list of possible causes instead of the specific one. When a page's template printed its own tag into the structured data instead of a value, the report quoted the parser's message and suggested looking for a trailing comma.
How we found it. Rebuilding the list of gaps a paying user would notice first from two weeks of audits of real sites, and measuring the structured-data finding against test pages.
What changed. Errors are grouped by cause rather than by where they occur, so the specific fix is given. When the parse fails inside an unfilled template tag, the finding names the tag and its line and says to fix the template.
Tests that hold it. test_a_json_ld_error_is_named_by_its_cause_not_its_column.py
A theme's menu icon and its code-block fonts were counted as brand typefaces
Release 1.3.126, published 30 October 2026. The release on GitHub.
What was wrong. The brand check told the same property manager its site used 11 different typefaces, 10 of them site-wide, and that each was a font file visitors download. Three were not typefaces a brand chose: the theme's icon font, whose only use is drawing the menu button, and two system fonts the theme sets for code blocks, which are already on the visitor's computer. A run an hour earlier had said 8 site-wide.
How we found it. Fact-checking a second copy of the same client report, reading the site's own stylesheets.
What changed. A font every use of which draws an icon glyph is recognised as an icon font, whatever it is called, and the system fonts used for code are left out like other system fonts. A monospace web font a brand does choose still counts. The same site now reads 8 typefaces, all web fonts its pages load.
Tests that hold it. test_an_icon_font_or_a_code_font_is_not_a_typeface.py
A property manager was offered video-game topics because of a word in its name
Release 1.3.124, published 28 October 2026. The release on GitHub.
What was wrong. With no topic typed, Check Engine works out what a site is about from its pages. On a property-management firm whose pages had nothing in common but the firm's name, it took a word from the name as the subject and suggested a dozen articles about video games. In the same report, five empty pages were described as a response a rate limiter or bot filter might hand out, an insecure embedded frame was called scripts or styles, and the first finding listed a blocked crawler among the possible causes when the pages were failing with server errors.
How we found it. Fact-checking a real report against the live site, page by page, before it went to a client.
What changed. A word now counts as a site's subject only if the site uses it in its own words, not just inside its name; when nothing qualifies, Check Engine says so. Empty pages that each carry their own title are judged as the site's pages. The insecure-content finding names what it found and quotes one. When most of what could not be read came back as server errors, the first finding names them as the cause.
Tests that hold it. test_a_brand_word_used_only_in_the_name_is_not_the_subject.py, test_empty_pages_with_their_own_titles_are_not_a_standin.py, test_mixed_content_names_what_it_found.py, test_a_crawl_cut_short_by_server_errors_says_so.py
A vet's homepage with an appointment link and a phone link was told it asked visitors to do nothing
Release 1.3.122, published 28 October 2026. The release on GitHub.
What was wrong. A veterinary hospital's homepage carries "Request an Appointment" and a tap-to-call "Call Us" link near the top. Check Engine said, at high severity, that no button or link on it asked the visitor to do anything, and, also at high severity, that the homepage did not state what the business does, because the first main heading held only the logo.
How we found it. Auditing three local businesses from source (a landscaper, a plumber and a veterinary hospital) and reading every finding against the page.
What changed. Check Engine now reads "request an", "book an", "make an appointment" and "call us" as actions, counts any phone or text link as one, and takes the first main heading with words in it as the headline. The same release stops telling Wix sites that nothing about their visitors is recorded, since Wix records visits itself, and stops counting an image's file name in a logo's alt text as a second spelling of the business name.
Tests that hold it. test_request_an_appointment_is_a_call_to_action.py, test_a_logo_h1_is_not_the_headline.py, test_a_wix_site_records_visits_without_a_tag.py, test_a_logo_alt_that_is_a_file_name_is_not_a_brand.py
An online job board was treated as a local business because of its mailing address
Release 1.3.121, published 28 October 2026. The release on GitHub.
What was wrong. A job board's contact page gives a mailing address, under a line about liking post, and nothing else local: no phone number, no opening hours, no local business markup. Check Engine treated the site as a local business on that address alone, reported at high severity that it had no local business markup and no phone number, and added three more local findings about a storefront that does not exist.
How we found it. Auditing a job board from source and reading every finding against the page.
What changed. An address labelled as being for post (mailing, postal, registered, billing or correspondence) no longer makes a site local on its own; a street address people are invited to visit still does. The same release stops calling an http:// link broken when a browser would reach it over https, and stops saying pages with Twitter/X tags have no card markup when only the card type is missing.
Tests that hold it. test_a_mailing_address_is_not_a_storefront.py, test_an_http_link_a_browser_upgrades_is_not_broken.py, test_twitter_tags_without_a_card_type_are_not_no_markup.py
A dental practice showing dozens of patient reviews was told it had none
Release 1.3.120, published 27 October 2026. The release on GitHub.
What was wrong. A dental practice's reviews page shows dozens of patient reviews, each signed "- Name" with an ordinary hyphen. Check Engine said the site had no customer reviews or testimonials on the pages it checked, the reviews page among them, and told the practice to add a testimonials section.
How we found it. Auditing a dental practice's site from source and reading every finding against the page.
What changed. Check Engine recognised a signature only after a long dash, and the first signature sat further below the reviews heading than it looked. A hyphen now signs a review after the end of a sentence, never before a day or "Noon", and a run of signed reviews below a reviews heading counts. The same release stops auditing a sitemap plugin's browser view of the sitemap as a page, and stops calling a short notice with two links out a category listing.
Tests that hold it. test_a_review_signed_with_a_hyphen_is_a_review.py, test_a_sitemap_view_is_not_a_page.py, test_a_short_notice_with_links_out_is_not_an_archive.py
A page was called a layout-shift risk because of a logo on every page
Release 1.3.119, published 27 October 2026. The release on GitHub.
What was wrong. A site's logo, with no declared size, sat in its header and again in its footer on every page. On a page whose one picture had its size declared, that made two unsized images out of three, and Check Engine said the page was at high risk of layout shift. Measured in a browser, nothing on the page moved. Image-heavy pages with the same logo were not flagged, so the verdict tracked how few pictures a page had.
How we found it. Auditing a law firm's site from source, reading every finding against the page, and measuring the layout shift of the flagged pages in a browser.
What changed. A picture that appears on most pages is read as part of the site's template, and a page is called high risk only when one of its own pictures has no size. A page whose own picture is unsized is still reported. The same release fixes two wrong findings from a blog audit: a theme's site-name heading was compared with a contact page's title instead of the page's own heading, and an avatar beside the author's reply in the comments was taken for the logo. It also starts asking servers for the homepage as Meta's search crawler, so a firewall refusing it is no longer reported as clean.
Tests that hold it. test_a_template_logo_does_not_make_a_page_a_layout_shift_risk.py, test_the_theme_s_h1_is_not_the_page_s_headline.py, test_a_comment_avatar_is_not_the_logo.py, test_meta_webindexer_is_probed_at_the_edge.py
A robots.txt block was said to stop AI assistants fetching a page a user asked about
Release 1.3.118, published 26 October 2026. The release on GitHub.
What was wrong. Check Engine's crawler list, and the AI crawler checker and the AI crawler directives article on this site, said that blocking ChatGPT-User, Perplexity-User or Meta-ExternalFetcher in robots.txt stops that assistant fetching your page when someone asks about it. The operators say otherwise: OpenAI says robots.txt rules may not apply to requests a user starts, Perplexity says its user fetcher generally ignores them, and Meta says its fetcher may bypass them. No audit finding printed the sentence, but the list shipped in every copy of Check Engine and these pages showed it.
How we found it. Re-reading each operator's own crawler documentation after the sources behind Check Engine's knowledge feed changed, then checking whether the app said the same as the feed.
What changed. The list, the knowledge feed and both pages now say what each operator says. Claude-User is still described as stopped by a robots.txt block, because Anthropic says it is. The same release adds Meta-WebIndexer, Meta AI's search crawler, to the crawlers Check Engine checks, and stops the competitor comparison calling a site that blocks Google-Extended absent from AI answers: Google's AI Overviews and AI Mode follow the Googlebot rules.
Tests that hold it. test_a_user_started_fetch_may_not_follow_robots_txt.py, test_meta_webindexer_is_a_citation_crawler.py, test_a_competitor_blocking_google_extended_is_still_in_ai_answers.py
Structured data in an old CDATA wrapper was said to be thrown away by search engines
Release 1.3.117, published 26 October 2026. The release on GitHub.
What was wrong. Some sites still write their structured data inside an XHTML-era CDATA comment. Check Engine reported a complete block written that way at high severity, as data that search engines discard entirely. It is not valid JSON as written, but we could not find a source showing what search engines do with it, so the claim went further than we could support.
How we found it. Auditing an art museum's site from source and reading every finding against the page, then looking for a source for the claim and finding none that tests this wrapper.
What changed. Check Engine now reads the block, and reports the wrapper at medium severity in words we can stand behind: it is not valid JSON as written, some parsers strip it, others may discard the block, and removing the wrapper makes it safe everywhere. Structured data broken in other ways is still high. The same release fixes two other wrong findings: parts of one serialised essay were called rival pages, and a Download link to an audio file was called vague anchor text.
Tests that hold it. test_a_cdata_wrapper_is_reported_as_far_as_it_can_be_sourced.py, test_parts_of_one_essay_are_a_series_not_rivals.py, test_download_on_a_file_link_is_its_purpose.py
A page written in two languages was reported as a mislabelled translation
Release 1.3.116, published 24 October 2026. The release on GitHub.
What was wrong. Check Engine compares the language a page declares with the language its text reads as. An English page that lists events in Spanish, with English headings and English description, was reported as reading as Spanish under an English label, and described as a translation competing with its original for the wrong readers.
How we found it. Auditing a public library's site from source and reading every finding against the page. English carried about three fifths as much of the page's text as Spanish did; a real translation carries none of its original language.
What changed. When the declared language carries a good share of the page as well, Check Engine treats the page as bilingual and says nothing. A translation labelled as its original is still reported. The same release corrects two other findings: a phone link written as a row of X's is called a placeholder rather than a vanity number, and a sitemap entry with the site's address written twice is named for what it is.
Tests that hold it. test_a_page_written_in_both_languages_is_not_in_the_wrong_one.py, test_a_row_of_xs_is_a_placeholder_not_a_vanity_number.py, test_a_sitemap_entry_with_the_address_twice_is_not_a_doubled_slash.py
Icon links named by the heading beside them were reported as having no text
Release 1.3.115, published 23 October 2026. The release on GitHub.
What was wrong. Accessible sites often build a card as an icon link that takes its name from the card's heading, with aria-labelledby. A screen reader announces the heading as the link's name. Check Engine did not read that attribute, so it counted every such link as having no anchor text at all, and told the site to add text that was already there.
How we found it. Auditing a city government's site from source and reading every finding against the page. On one page, all eighteen links Check Engine counted as empty were icon links named by the heading beside them.
What changed. Check Engine now reads a link's name from aria-labelledby first, the way a screen reader does. A link that points at nothing still counts as empty. The same release fixes two other wrong findings: a favicon check the site refused was reported as a missing favicon, and one dropdown repeated once per location was counted as many form fields.
Tests that hold it. test_a_link_named_by_aria_labelledby_has_a_name.py, test_a_refused_favicon_probe_is_not_a_missing_favicon.py, test_one_question_asked_per_location_is_one_field.py
Template text a page hides from everyone was reported as live and unfinished
Release 1.3.114, published 22 October 2026. The release on GitHub.
What was wrong. Page builders can leave unused template blocks in a page, with words like Lorem ipsum and Heading 2 text goes here, and switch them off in the page's own stylesheet so that no visitor ever sees them. Check Engine read the words in the HTML and reported, at high severity, that unfinished content was live and indexable, with advice to replace it or take the page out of search.
How we found it. Auditing a charity's site from source and reading every finding against the page. In a browser every one of the blocks was switched off and took up no space.
What changed. Text the page hides from every visitor, with the hidden attribute, an inline style or a rule in its own stylesheet that applies at every screen size, is no longer read as placeholder text. The same words where a visitor can see them, including a block shown only on phones, are still reported. The same release fixes two other wrong findings: a docs site's section label read as a second brand name, and the weights of one font counted as separate typefaces.
Tests that hold it. test_a_template_block_the_page_hides_is_not_live_placeholder.py, test_a_section_label_beside_the_logo_is_not_a_brand.py, test_the_cuts_of_one_typeface_are_one_typeface.py
A link whose server hung up on Check Engine was reported as broken
Release 1.3.113, published 21 October 2026. The release on GitHub.
What was wrong. Check Engine checks a sample of the links a site makes to other sites. Some servers accept the connection and then close it without answering when the request comes from an audit tool, while a browser gets the page. Check Engine recorded that the same way as a server it could not reach at all, and told the site that working links were broken and should be updated or removed.
How we found it. Auditing a travel blog from source and reading every finding against the page. Both of its broken links opened in a browser, and the same request carrying Check Engine's name got no answer at all.
What changed. Check Engine now tells a server that hung up from one it could not reach. A hang-up is listed with the links that refused an automated request, as links Check Engine could not check, and a link whose server cannot be reached at all is still reported as broken. The same release fixes two other wrong findings: a page quoting the words coming soon was read as unfinished, and a page whose two H1s say the same thing was counted as having more than one.
Tests that hold it. test_a_server_that_hangs_up_is_refusing_not_gone.py, test_two_h1s_that_say_the_same_thing_are_one_topic.py, test_a_quoted_coming_soon_beside_this_page_is_not_a_placeholder.py
A Cloudflare site's bot check was reported as AI crawlers refused
Release 1.3.112, published 21 October 2026. The release on GitHub.
What was wrong. Check Engine asks a site's server for the homepage as each AI crawler, from the user's own computer. Cloudflare checks real crawlers by the network address they come from, so to Cloudflare those requests are crawlers pretending, and it may challenge or turn them away while serving the real crawlers normally. Check Engine reported that as AI crawlers refused by the server, on two sites at critical severity and with a warning that search was blocked too, although the real crawlers read both. A server that asked a crawler to pay, with HTTP 402, was counted as having served the page.
How we found it. Before any ad said sites were blocking AI crawlers, we tested the check on twenty sites behind Cloudflare and compared each verdict with what each crawler is really served. Three sites block AI crawlers by name, and Check Engine was right about them; two were false blocks.
What changed. When Cloudflare challenges the request, or refuses Check Engine's Googlebot request as well, Check Engine now says it could not test those crawlers from here, and where the real answer is: Cloudflare's security events, and Search Console for Google. A rule that refuses AI crawlers by name while Googlebot is served is still reported as a block. A 402 is reported as a request to pay, never as a page served. The same release fixes 24 other wrong findings, found by auditing real sites of several kinds and reading every finding against the page.
Tests that hold it. test_a_crawler_cloudflare_cannot_verify_is_not_a_blocked_crawler.py, test_a_crawler_asked_to_pay_was_not_served_the_page.py
The installed app kept no record of what its helpers did
Release 1.3.110, published 14 October 2026. The release on GitHub.
What was wrong. The desktop app starts Check Engine's engine with its output going nowhere. If the helper that renders pages in a browser was missing or broken, nothing said so: rendering was skipped, the checks that need it reported that they could not confirm, and the report looked like an audit where rendering had simply found nothing. Starting the installed engine with an empty home folder left no log at all.
How we found it. Another of our apps found a doubling in speed by reading its log. Check Engine had no log to read, so a support question would have been answered from the customer's description of it.
What changed. The engine now writes a log file in the .docket folder in your home folder: the version, the licence mode (never the key), and for the render helper whether it was found, where, and whether it runs. It adds a line for each audit that names the host and nothing more. The file stays on your Mac and nothing sends it. The build now starts the finished app with an empty home folder and refuses to ship unless the log says the render helper runs. It then points the engine at a file that cannot run and requires the log to say so.
Tests that hold it. test_the_installed_engine_keeps_a_log_that_names_its_helpers.py
Five wrong findings, found by auditing our own sites
Release 1.3.107, published 13 October 2026. The release on GitHub.
What was wrong. Check Engine read a date followed by a sentence ("last updated 2026-09-15 The fastest way:") as the street address "15 The fastest way". That made a Mac app a local business, and its two top findings were high severity: no phone number and no LocalBusiness markup. It read a licence date followed by a year range as a phone number. It treated the home city in a person's markup as a business address. It counted pages whose canonical tag names a different page as competing with that page. And one index page could pull a whole series into one "target the same search" group: a page for each of 29 states and their index counted as one search.
How we found it. We audited all seven of our own sites and read every finding against the live page. Five findings were Check Engine's mistake, not the site's.
What changed. A day in an ISO or written date, a month and year, and a run of years are no longer an address or a phone number. A person's address no longer makes a local business. A page that names another as its canonical stops competing with it. A section index no longer glues its pages into one group, and Virginia and West Virginia are two places.
Result. On the same crawl before and after, the app site's score went from 89.9 to 92.3 and its four local findings went. A portfolio site went from 91.8 to 98.0. The series site's competing-pages finding fell from 53 pages in 6 groups to 13 in 6; the pairs left are calculators that share a title ending and two overlaps that look real.
Tests that hold it. test_a_date_and_a_sentence_are_not_a_street_address.py, test_a_licence_date_and_a_year_range_are_not_a_phone_number.py, test_where_a_person_lives_is_not_a_local_business.py, test_a_page_that_names_another_as_its_canonical_does_not_compete_with_it.py, test_a_hub_does_not_glue_a_series_into_one_search.py
The link check looked at one host thirty times, and the topic step threw away its answers
Release 1.3.106, published 13 October 2026. The release on GitHub.
What was wrong. The outbound link check took the first 60 links in page order. On our own site 30 of its 61 targets were Buy and Download links to the same host, so the sample said little about the rest. Separately, the step that suggests topics asked a search engine's autocomplete for 18 commercial phrasings for each seed word and then discarded every answer. The stage between the link check and rendering took 30 to 34 seconds on the installed copy.
How we found it. Measuring the installed copy one stage at a time. With a counting wrapper on the same code, two seed words made 62 requests and spent 32.3 seconds in that step.
What changed. The sample takes at most 8 links to any one site, and the report says "at most 8 to any one site" so it does not claim to have checked more. The topic step now asks only for the phrasings it keeps.
Result. Side by side against the live search engine, the same seeds made 62 calls in 38.3 seconds before and 26 calls in 13.0 seconds after, and the suggestions kept were identical in both pairs of runs (112 and 212). Dropping the commercial phrasings could in principle hide a later question behind an earlier completion; we did not see it in either pair.
Tests that hold it. test_the_topic_suggestions_connector_asks_only_for_what_it_keeps.py, test_the_outbound_sample_is_spread_over_hosts_not_taken_in_page_order.py
Outbound links waited in line behind the audited site's pace
Release 1.3.105, published 13 October 2026. The release on GitHub.
What was wrong. Every outbound link check went through the gap meant to protect the site being audited, so 60 requests to 60 other servers queued behind a pace of four a second. The stage took 16.8 seconds of a 101-second audit. On a site whose robots.txt asked for ten seconds between requests it would have taken 600 seconds for 60 one-off links, which is why the check had a time budget that left links unchecked.
How we found it. Measuring the installed copy one stage at a time.
What changed. Each other host now gets its own clock and its own gap, so different hosts are checked side by side. The audited site's own links keep the polite gap. A link the clock had no time for is now left out of the results, not recorded as status 0, which the report would have called unreachable.
Result. Installed, the links stage took 11.0 and 10.4 seconds against 16.8, and the whole audit 96.9 and 96.7 against 101 to 105. It is not under 5 seconds because 30 of the 61 targets went to one host, which 1.3.106 addresses.
Tests that hold it. test_outbound_links_are_paced_per_host_not_by_the_audited_site.py
A page that followed Check Engine's own advice was still flagged
Release 1.3.104, published 10 October 2026. The release on GitHub.
What was wrong. The finding about old image formats prints a picture element with a WebP source and the JPEG kept as the fallback. A four-page test site with exactly that markup still had all 16 of its images reported as old formats, because the check read each image's src, and in that markup the src is the fallback by design. Following the tool's advice did not clear the tool's finding.
How we found it. Writing the how-to guide for that finding, we audited a four-page test site with plain image tags and again with the picture markup. Plain tags reported 16 images, correctly. The picture markup also reported 16.
What changed. An image in a picture element with a WebP or AVIF source, or with a srcset that names only such files, now counts as modern. A picture of JPEG sources, a mixed srcset and a plain image are still reported. JPEG XL is not counted: MDN, read on 10 October 2026, says its browser support is not yet universal.
The guide for that finding, which says the bug existed.
Tests that hold it. test_a_picture_with_a_webp_source_is_a_modern_image.py
A franchise and a church: four findings that were false
Release 1.3.103, published 9 October 2026. The release on GitHub.
What was wrong. On a plumbing franchise with a section for each location, Check Engine said 309 pages in 69 groups targeted the same search. They were the same services in different towns, each location writing its own template around the place name. It also told that site that marking up its reviews of itself would earn stars in search, which Google's review-snippet guidelines (read 9 October 2026) rule out for a business reviewing itself. On a church site it said there was no LocalBusiness markup on a local business, gave it a LocalBusiness block to paste, and told it that it had no customer reviews.
How we found it. Two offline audits of real sites, each finding read against the pages. The rest of what Check Engine reported on both sites was true.
What changed. A town, county or state named in a title now sets two pages apart, and a contact page no longer competes with a service page. Reviews count only on a page about a thing, such as a product, a recipe or an event. A place of worship is recognised from its name and is offered Church markup, and the no-reviews finding applies to businesses only.
Result. On the same crawl the franchise went from 309 pages in 69 groups to 13 in 6. The 13 include one town's service written twice, which do compete.
Tests that hold it. test_the_same_service_in_two_towns_is_two_searches.py, test_a_business_reviewing_itself_cannot_earn_stars.py, test_a_church_is_not_a_local_business.py
Refresh said everything was up to date over rows that were a week old
Release 1.3.102, published 9 October 2026. The release on GitHub.
What was wrong. Refresh answered "Up to date: 4 sites, nothing changed" while every row on the watch list read "7d ago" on a daily schedule. The button compared the lengths of two lists. The scheduler runs only while the app is open and took one overdue site a minute, so a copy that had been closed for a week was minutes from current and said nothing.
How we found it. From a screenshot of the watch list.
What changed. Refresh now checks every overdue site, one after another, and says how many it started. An overdue row says so, a check that failed shows its reason on its row, and the header says checks run while the app is open and catch up when it opens.
Not proven. We could not read the copy in the screenshot, so we do not know whether it had been closed or was failing. The new row text shows which.
Tests that hold it. test_pressing_refresh_on_overdue_sites_checks_them.py
A text-message number was called not clickable, and a business with no shop was asked for an address
Release 1.3.97, published 2 October 2026. The release on GitHub.
What was wrong. A business that publishes its number only as sms: links, by design, was told the number was not clickable and to wrap it in tel: links, which invites calls the business does not take. A service-area business that declares where it serves was told to put a street address in its footer, the opposite of Google's guidance for such a business.
How we found it. Both showed up in the top five findings of the business in our own public sample report. Read against its live pages, the home page had six sms: links, no tel: link and a declared service area.
What changed. sms: and smsto: links now count as click-to-contact, and where the finding still applies its fix offers an sms: link for a number that takes texts. A service-area business is no longer asked for an address. Both were tested in both directions: the real case is left alone and the same site without the sms: link, or without the service-area signal, is still reported.
Tests that hold it. test_a_text_only_number_and_a_service_area_are_not_gaps.py
Tags added by JavaScript were reported as missing
Release 1.3.80, published 23 September 2026. The release on GitHub.
What was wrong. Check Engine told a games publisher that analytics was missing from 30 of 30 pages and that no other analytics ran anywhere. The site loads Tag Manager, GA4, Google Ads and the Meta pixel with JavaScript, so the served HTML had none of them and a browser had all of them. Two checks about JavaScript also flagged a framework's login page as invisible to AI crawlers and a password-reset page as very thin, though nobody searches for either.
How we found it. A sweep of well-built sites, with a deliberately defective control site that had to fire and did. Each high-severity finding was read against the live page. Most of the others were true: one browser maker's home page really does carry no structured data.
What changed. When a render finds tags on a page whose served HTML showed none, the served-only gap is stood down. Login and password pages are skipped by both JavaScript checks. The release also fixed a case where a render cleared the flag on a page that is an empty single-page-app shell, which is still one word of content after rendering.
Not proven. One payments company's very-thin finding could not be rechecked that day, because the audit refused to run while a build was in progress. It was carried to the next round.
Tests that hold it. test_a_tag_the_render_found_is_not_a_gap.py, test_a_login_page_is_not_owed_to_ai_crawlers.py, test_a_short_page_is_not_a_javascript_page.py
15 cases where a finding looked wrong
The releases above say what changed. These 15 cases say what the mistake looked like to the person reading the report, and what to check on your own. They were first published as separate guides and are collected here. They are not release records: none carries a release number or a test list, because the guides never claimed either. No site is named.
- A finding's count is not decoration
- When two findings count the same pages twice
- A pixel on part of the site, and on no pages
- When an audit blames the site for one template
- Why an audit finds reviews you do not have
- Telling your site's furniture from its content
- Not every address on your site is yours
- Which host is that finding about?
- An @id is a pointer, not a definition
- The markup on your page may not be about you
- A completeness check assumes your type is right
- What a dead robots.txt rule actually costs
- When a report calls your decision a defect
- A fix that hid a real problem somewhere else
- When a report says nothing at all
A number that counted the wrong thing
A finding's count is not decoration
On a hospital's report, a finding's badge read sixteen profiles directly above the headline “Only two recognised profiles in sameAs”. The headline counted the profiles the site had. The badge counted the ones it did not: the rest of the tool's own list. That is more than a wording problem, because the count feeds the score. The deduction grows with the logarithm of the count, so the inflated badge lifted a low severity finding above the medium one for having no sameAs at all.
The phone check had the same fault in another form. It counted links, not pages, so one bad number in a header and a footer became two pages, with “and one more” printed under a list that showed the only page there was. Both checks now count what their headlines count.
On your own report. Read the badge against the headline and the count against the URLs listed, then ask what one fix would cover. If one edit to one template clears every instance, the count is measuring evidence, not work.
Registered checks. ai.entity, cvr.unusable_phone.
See also: How findings are ordered.
When two findings count the same pages twice
On a shoe retailer's crawl of about thirty pages, the report carried two findings with the same check, the same fix and titles that differed only by a count of pages with incomplete product markup. Most pages sat under both, so the total was larger than the crawl. The findings were keyed on the type and the list of missing fields, so one type split into as many findings as it had field combinations. It is now one finding per type, with the combinations in the detail.
The explanation beside the count had two faults of its own. It asserted that a listing or variant picker repeats the type, and on a site where the blocks were references it sent readers hunting for something that was not there. And the sentence that does say the blocks are references was shown only when blocks outnumbered pages, so the ordinary shape of one reference per page never got it.
On your own report. Add the counts. If the total is bigger than the pages crawled, two findings overlap: compare their URL lists and their fixes before treating them as two jobs.
Registered check. schema.incomplete.
See also: An @id is a pointer, not a definition.
A pixel on part of the site, and on no pages
A financial institution's report listed six advertising pixels as installed on only part of the site, each with a page count of zero. A pixel on no pages is either absent or somewhere the check could not count, and here it was the second. The installed list merged the served HTML with the rendered page, so it saw tags that JavaScript adds. The per-page count read the served HTML alone, so the same tags scored zero, which is below every coverage threshold. The same seam kept a site that loads every tag through a container from ever reaching the warning about too many tools on one page.
Counting from the rendered pages instead would have been worse, because only a sample is rendered and “we only looked at a few pages” would become “it is only on a few pages”. Coverage is now judged only for tags visible in the served HTML, and the finding names the tags it did not judge.
On your own report. When a headline says part of the site and the number says zero pages, believe the number and distrust the headline. View source to see whether the tag is in the HTML at all, and ask the ad platform which pages fired its pixel.
Registered checks. mar.ad_pixels, mar.stack.
See also: The tracking checks.
When an audit blames the site for one template
A tool reported that a site had no share cards. The pages people actually share already had complete card markup; the ones without were a listing, an archive and other pages made by a different template. The count was accurate and the verdict was not. Nearly everything an audit measures is set once, in a layout, a partial or a component, so findings cluster by template, and a count with no location both overstates the work and understates the danger. A majority is not a site.
The check now tells every page from most of them. A tool should also give a proportion and name the URLs, so the grouping shows without an export.
On your own report. Sort the affected URLs by path: the grouping usually falls out at once. Then look at one page that was not flagged. If a page of the same kind is correct, the template was fixed once and not everywhere.
See also: How to tell whether an audit tool is lying to you.
A finding inferred from text, markup or a name
Why an audit finds reviews you do not have
A software foundation was told “Reviews are shown but not marked up”. The evidence was the word review used as an ordinary verb, and the site's own sentence saying it does not provide testimonials, counted as proof that it does. A wildlife charity's joke about five-star accommodation for bees was quoted back as displayed customer ratings. One mistake was ours alone: a street-address pattern matched “that way” in this project's own marketing copy, decided a Mac software company was a local business, and produced six findings, two of them false at high severity.
The phrase list is short and specific now, and the address pattern needs a capitalised street name, which knowingly misses some real addresses. The two errors are not equal. A missed review section gives advice you can ignore. A false match sends you looking for a section you do not have.
On your own report. Read the quoted evidence before the headline, and ask whether the page is about the thing or doing it. A policy page about reviews matches every phrase list and is not the signal.
Registered checks. local.reviews, local.applicable.
Telling your site's furniture from its content
Almost every judgement about what a page says depends on telling its furniture (navigation, header, footer, share buttons) from its content. We got it wrong five times in five checks, the fifth in the fix for the fourth. Counting links measures the template: an author archive carries dozens of links and only a few are the author's articles. The share of words inside links is better, until a page carries bare anchor tags with no navigation element around them. Then nothing is stripped, a genuine stub looks like an index, and an empty page is excused as an archive.
What separates a label from an entry is length: an anchor needs at least four words to count as an entry. So there are three tests, each failing somewhere: repetition across pages, position inside a navigation element, and length.
On your own report. When a tool calls a listing page thin, ask what it counted. If it counted links, it counted your template.
Registered check. content.thin.
See also: How to run a content audit.
Not every address on your site is yours
The check that tests whether published email addresses can receive mail has to decide which addresses are yours. A software forum's how-to contained example addresses at a documentation domain, which the platform turned into links, so the site was told to publish mail records for a domain it does not own. The finding was true and the instruction was impossible. The remedy is now offered only for a domain the reader can publish records for.
A law firm's share-by-email button has an empty recipient on purpose, but a branch tested the verdict and not the reason, so “not a domain” became “cannot receive mail”. That case is skipped, not renamed, because it is not an address. The case that must still fire is a bare server address, which can never have mail records.
On your own report. Look at the domain in the address. If it is not yours, no instruction about mail records applies to you.
Registered check. cvr.dead_contact.
See also: What a dead contact address is.
Which host is that finding about?
An audit said a streaming service's certificate was close to expiry. The crawl began at the www address and followed a redirect, and it was the destination host's certificate that was expiring. Checking the address that had been typed showed months left, so the sentence read as false. The finding now names the host.
The same shape appeared in two more places. A bakery chain was told its plain-http address did not redirect to https, true of the www spelling and not of the bare domain, which are separate hosts with their own certificates and redirect rules. And on a large retail site the plain-http address returned an error, which the report called an insecure copy of every page. Reachable, answering and refusing are three situations, and only the first is the problem it described.
On your own report. Find the host before you check the claim, and test both spellings over plain http by hand.
Registered check. security.https.
See also: How to redirect http to https, www or no www.
Structured data read as something it was not
An @id is a pointer, not a definition
An audit reported several pages as having incomplete Organization markup, but the Organization was defined once, with its name, URL and logo, and the flagged blocks were references to it by @id. Two nodes with the same @id are one node, and a bare reference is complete as written; the most used SEO plugins emit it by default. Reproduced with a controlled graph, the check counted the reference as a second, worse copy of the thing it pointed at.
The real defect looks the same from outside. A search engine reads one page at a time, so if every block of a type on a page is a bare reference and nothing there defines it, it resolves to nothing, and neither a validator nor a rich result test says so. The repair is to define the entity on every page that references it, or to remove the stub, not to add a name to each stub.
On your own report. View source on a flagged page and search for the identifier. If it appears once as a definition and once as a reference, your markup is fine and the finding is wrong.
Registered check. schema.incomplete.
See also: How to fix structured data errors.
The markup on your page may not be about you
An audit said a national food-delivery marketplace was missing its address, telephone, opening hours and price range, with two fields present but blank. The only structured data on the page was a list of restaurants, other people's takeaways, and the check had judged the first node it reached as if it belonged to the publisher. One property settles whose data it is: itemListElement, whose entries are the things a page lists, not the thing it is.
The finding stayed and changed its subject, because thin listing markup is real and a chain publishing its own branches in a list still needs complete entries. It now describes the businesses the page lists. A second sentence, that the tool had found restaurant markup, was literally true and read as “we think you are a restaurant”.
On your own report. Ask whose data is missing. If the finding names fields you would never publish about yourself, the node is probably not yours: search the page source for itemListElement.
Registered checks. local.schema, local.applicable.
See also: An @id is a pointer, not a definition.
A completeness check assumes your type is right
A property portal's homepage declared a Product node that held the company's name, address on the web, logo and profile links. It had no image, so the check advised adding one. The advice was correct for the type and the type was wrong: logo is not a property of Product, and with no offer, price or review the block was never going to produce a product rich result. A completeness check works by lookup, so the type is an input it never judges. It now speaks up only when an Organization-only property is present and every product signal is absent.
The same shape in miniature: a large public-sector site wrote headLine for headline. JSON-LD keys are case sensitive, so no headline was read at all. The check matches on case and never on spelling, because a confident wrong guess is worse than a generic sentence.
On your own report. Read the type before the missing fields, and search your block for the property without regard to case.
Registered check. schema.incomplete.
See also: How to fix structured data errors.
A decision called a defect, a fix with a cost, a gap nobody reported
What a dead robots.txt rule actually costs
A computer manufacturer's robots.txt named six retired AI crawler tokens, and we reported at medium severity that those crawlers were reading pages it meant to withhold. That was false. The catch-all group carried seventeen Disallow rules, and all seven in the AI group were repeated there. A crawler that matches no group falls back to User-agent: *, so these fell back to a stricter group and lost no coverage. Only a Crawl-delay went unheard.
A rule's effect belongs to the group, not to the token. A dead name costs nothing when the catch-all is stricter or carries the same rules, or when a live replacement sits in the same group. It is a real exposure only when the catch-all is looser. The comparison is an exact match on the rules, deliberately crude, so the uncertain case stays reported.
On your own report. Compare the AI group's rules with the catch-all's. If every rule also appears under *, the dead name has cost you nothing.
Registered check. ai.dead_crawler_directive.
See also: How often AI rules name a crawler that no longer answers, Which AI crawlers to block.
When a report calls your decision a defect
A national broadcaster names the AI crawlers that decide whether it can be cited and disallows them, on purpose, as editorial policy. We called that critical and capped its score. The opposite case writes the same line: a blanket User-agent: * rule added for an unrelated reason sweeps up every crawler, and nobody decided it. What separates them is whether the crawler is named.
Then a worse failure. For a file that named two crawlers and let a wildcard catch a third, the report said none of them was named, a sentence that set the severity and told the reader to re-allow two crawlers they had just blocked deliberately. The set is now split: named blocks are a notice, wildcard-caught ones are the interruption, and an allowlist that names the AI crawlers it admits counts as policy. The paste-ready block is derived from what was found.
On your own report. Open the file and look for the name. Check any report sentence that says none or every against the file itself, because that is where reports go wrong.
Registered check. ai.crawler_access.
See also: How to fix AI crawler access, A rate limit is not a block.
A fix that hid a real problem somewhere else
A preprint archive's logo link is named “archive home”. The brand-name check strips navigation words from such a name, so “home” came off and “archive” was recorded as a second name for the organisation. The fix made the weakest source, a link name, agree with the one signal a site cannot easily fake: its own domain. The first version asked that of every residue, and it broke a case a test in the suite holds, a logo naming one institution on a site titled after another, which is exactly the disagreement the check exists to find. One site's false positive had been traded for another site's false negative, and a false negative makes no noise.
The narrower condition applies only when a navigation word had to be removed, because that is what made the string suspicious. A company whose domain does not contain its name loses that one source in that case, and its declarations still count. A related guard: a name seen once across dozens of signals is a single odd page, not a second identity.
On your own report. When a false positive you reported gets fixed, ask what the fix cost. A condition that makes a rule quieter about you probably made it quieter about someone else.
Registered check. brand.name_consistency.
See also: Where the name guess comes from, Brand consistency.
When a report says nothing at all
Broken-page checks sort responses into buckets: server errors, not found and gone, refused requests, and no response at all. The rest of the four-hundred range, where the server understood the request and rejected it, was never asked about. So a URL on an audited site, linked from its own pages and rejected by its own server, produced no finding from any registered check. It was found by accident, while checking that a change to the external link rule had not silenced the internal one.
Nobody complains about silence. The page is not a not-found, so every report that counts those shows the site clean while a search engine gets the same refusal as the visitor. Statuses that mean the crawler itself was turned away, such as a rate limit, stay out on purpose.
On your own report. Compare the statuses your site really returns, from logs or a crawl export, with the ones your report names. Anything in the first list and not the second is a blind spot.
Registered check. index.broken.
See also: Pages an audit could not reach, A rate limit is not a block.
If a finding on your site looks wrong
Send us the address of the page and the text of the finding through the contact page. We read the finding against the page itself. If Check Engine was wrong, the fix gets a test like the ones above.
$59once
For Macs with Apple silicon or Intel, macOS 12 or later, and 64-bit Windows
Priced in US dollars. Checkout shows your currency and any local tax.
On a Mac? Get the Mac version.
Launch price: $29.50 once, through 30 November. From 1 December: $59. Buy applies the code FOUNDINGDOCKET for you; if the checkout shows $59, enter it under ‘Have a discount code?’.
Screaming Frog is $279 a year. Ahrefs plans with Site Audit run $129–$449 a month. Check Engine is $59 once. Screaming Frog’s price read 4 November 2026, Ahrefs’ 9 November 2026.
30 days, no conditions, no questions asked. Refund policy
Common questions
Has Check Engine ever reported something that was not true?
Yes. This page lists 46 releases that fixed findings or behaviour that were wrong, each with the day it was published and the tests that hold the fix.
Why would an audit report a problem my site does not have?
Usually because a check inferred something from text, a count, a tag or a name, and the inference failed on a site shaped differently from the ones it was written for. The 15 worked cases on this page are 15 such failures in Check Engine's own checks, each with what to check on your own report.
A finding's count and its headline disagree. Which is right?
Usually the headline, because a person wrote it and the badge came from a variable. Compare the count with the list of URLs under the finding before acting on it. One worked case here shows a count of the wrong thing lifting a minor finding above a more serious one.
What should I do if a Check Engine finding looks wrong on my site?
Send the address of the page and the finding to us through the contact page. We read the finding against the page, and if Check Engine was wrong the fix gets a test.