Limits
The engine's hard caps, what goflag deliberately does not check, and the failure modes worth knowing about before you trust a green run.
A tool that only documents what it catches is not honest about what it misses.
Engine caps
Most of these are compiled in, not configurable. They exist so a run on a large
site finishes rather than melting somebody's laptop or somebody's origin. The
values are listed in the CLI reference. The one
exception is the cap on pages scanned for links: the CLI always sets it to the
run's own page budget, so it moves with --max-pages and with the size of a
structural selection.
The caps you can move are --depth (default 2), --max-pages (default
200) and --coverage. --max-pages is not a plain ceiling: when a sitemap is
found the run selects its pages by structure (--coverage structural, the
default) and the budget is raised to the size of that selection plus five, so
setting it below what the selection needs changes nothing about the pages it
audits. It cuts the run short under --coverage all, or when no sitemap was
found — and, on a structural run, it still bounds the room left for pages the
crawl reaches by following links.
--include and --exclude filter links the crawl discovers. They do not filter
the sitemap selection, which is seeded straight into the crawl, so on the default
path they cannot make a run smaller.
A report can cover part of your site in four ways
diagnostics.truncated is true only when a page or link cap actually cut the
crawl or the link audit short; the terminal then prints
note: results truncated by a page/link cap. It is not the only way a run sees
less than the whole site:
- a structural run samples families of pages built from one template. It says
so in
diagnostics.coverage(considered,selected,families) and in the terminalCOVERAGEline, withtruncatedstillfalse; - pages the run selected and never reached are counted in
diagnostics.sitemap.uncrawled, again withtruncatedstillfalse; - a sitemap over 5,000 URLs, or an index with over 50 child sitemaps, is cut
during discovery — silently, with no warning and
truncatedstillfalse.
A run that truncated, sampled, or missed selected pages is a report about part
of your site, and a green verdict on one means very little. Read
diagnostics.coverage and diagnostics.sitemap.uncrawled beside truncated.
What goflag does not check
These are decisions, not oversights:
- Performance. No Core Web Vitals, no Lighthouse score, no bundle analysis. Different tools, different failure modes, and both already well served.
- Accessibility. Nothing here is an a11y audit.
axeand friends do that properly. - Content quality. No keyword analysis, no readability score, no rank tracking, no competitor comparison.
- The static/hydrated gap is reported, never judged. When goflag escalates to
Chromium it holds two readings of the
<head>and knows exactly which tags only the browser has — the failure mode everyog.*rule is blind to, since each judges the declaration it was handed rather than which pass handed it over. No rule reads that delta.goflag previewdraws it, and the report carries it underextractions[].hydrationwhen both readings exist.
Structured data validity. JSON-LD presence is used as a signal that a page's head is not empty. Its correctness is not judged.
- Images. No
altaudit, no weight checks, no format advice. - JavaScript errors. The headless path renders a page; it does not report what the console said while doing it.
- Anything requiring authentication. goflag sends no credentials and has nowhere to put them. It audits what an anonymous crawler sees, which is also the point.
Failure modes worth knowing
Rate limiting looks like breakage, and it fails the build. A 429 from a
link target is a blocked verdict. A 429 — or any non-2xx — from a page the
crawl asked for is an entry in unreachablePages, which is always a red
verdict, so it fails the gate even under --fail-on error. A page that never
answers at all (timeout, reset, DNS) is asked once more from the back of the
queue and counted as unreachable only if the second attempt fails too; a page
that needed the retry is reported as a warning line, not a finding. Raising
--timeout helps. Lowering --max-pages does not, under structural coverage —
and neither do --include / --exclude, because the pages a structural run
audits come from the sitemap and are seeded into the crawl without passing
through those globs. They filter links the crawl discovers, not the selection it
starts from. To make such a run genuinely smaller, use
--coverage all --max-pages <n>, or --no-sitemap with the globs. Auditing your
own origin from CI usually avoids the whole class.
Anti-bot filters return 403. These are triaged apart from real breakage: the
verdict is blocked, not broken, and they are left out of
summary.brokenLinks. They are not warnings, though — the entries sit in the
report's brokenLinks array, print under "Broken links" in the terminal, and
make the verdict yellow, so they fail a default --fail-on warning gate. This is
one reason --no-external belongs in a CI gate.
Soft 404s are detected heuristically, and only on links. A link target that
answers 200 with a body under 2 KB matching "not found" is flagged, with the
verdict warning. Two consequences: a page of your own site that the crawl
visited is never tested for it, and a soft 404 served with the site's usual
layout is over the size limit and never caught. False negatives are the norm
here, not the exception.
The length thresholds are conventions, not specifications. title.length and
description.length cite no standard, because there is none to cite. They are
labelled as heuristics in the catalogue.
Client-rendered metadata needs the browser path. By default, a page with an
empty-looking head is re-rendered in Chromium. With --static, it is judged as
served, which is correct for a crawler, and misleading if you expected the
runtime result. See Install before opting in. The same
thing happens without the opt-in when Playwright or its Chromium binary is
missing — the escalation is skipped and the page is judged as served — but the
run says so: the pages that wanted the browser are counted in
diagnostics.warnings, with what could not be started. On an SPA those pages
carry a full column of phantom title.missing / description.missing findings,
so install Chromium before you believe them.
Translations are paired by pathname unless a cluster is declared. A route is
the URL with its locale segment removed, so on its own goflag only recognises a
pair when the slug is identical in every locale. Two declarations fix it, for the
matrix and for the hreflang checks alike: xhtml:link in your sitemap, or
reciprocal hreflang in the two pages' <head>. The <head> route needs both
pages crawled, so under --coverage structural it is often
silent on the large sampled families where the sitemap one still works. A site
that translates its slugs and declares neither way gets one hole per locale on
pairs that are fully translated, and a question raised about each half. See
Translations.
A baseline can hide a lot. --regressions-only passes builds on sites with
known defects. That is what it is for. The output never goes green while findings
are being let through: it prints REGRESSION GATE, how many known findings are
not gating the build, and how old the baseline is. (A green CLEAN appears only
when the baseline holds nothing and nothing is new.) But if nobody reads it, the
number does not help. See baseline gating.
Known false positives, and why they are documented
Capturing the first baselines on four production sites surfaced five ways goflag was wrong. Two are worth reading because they shaped the design:
- The path segment
/cvwas read as a locale, the mistake that ended locale guessing. The full story is in Translations. - A trailing slash was stripped before probing, which declared 159 working citation links dead and sent somebody to fix content that was already correct.
Four of the five are fixed outright. The /cv one is contained, not cured:
the locale axis stopped guessing, but the matrix still splits a path on its
leading segment whenever that segment merely looks like a tag — so on a site
with a declared axis, /cv still becomes a locale column and still invents
holes, and no flag suppresses it. The full account, and what to do meanwhile, is
in Translations.
They are published rather than buried because a tool that reports defects has to be auditable itself, and because the second one cost somebody an afternoon.
Reporting a wrong finding
A finding you believe is wrong is worth an issue: include the URL, the rule id,
and the --json output for that page. id is stable, so a report from three
weeks ago still identifies the same finding today.