Your page is perfect. Google no longer sees it.
goflag is a CLI that reads your site the way a crawler does, and fails a CI step before an invisible defect ships.
npx @goflag/cli https://example.comA tag changes.
day 0Without goflag
It ships.
day 0The page leaves the index.
weeks laterSomeone asks why traffic fell.
months laterWith goflag
CI raises the flag.
4 min laterA developer or an agent fixes it.
minutes laterIt ships clean.
still day 0What a browser cannot show you
Each of these renders perfectly. The defect is in the part of the page nobody looks at, and the cost is paid in a search index you never see.
What you see
A page that renders exactly as designed.
What a crawler sees
<link rel="canonical" href="/another-page">Consequence
The page asks to be dropped in favour of another. It will be.
What you see
Four locales, every one of them reachable.
What a crawler sees
no <link rel="alternate" hreflang> anywhereConsequence
Each translation competes with the other three instead of consolidating with them.
What you see
A live production site, serving traffic.
What a crawler sees
robots.txt → User-agent: *
Disallow: /Consequence
No page is ever fetched, so the index directive on every page is never read.
The whole interface
The JSON report is the interface: pipe it into scripts, diff it between runs, gate CI on it, or hand it to an AI agent. The terminal output is a render of it.
$ npx @goflag/cli https://example.com
goflag https://example.com/
RED FLAG 128 pages crawled, 128 scanned
3 broken links 2 missing translations 14 SEO issues 1 site issue 0 unreachable pages
Broken links
https://example.com/blog/hreflang-basics
[404] https://example.com/guides/canonical-tags
[blocked 403 forbidden] https://api.partner.example/status
Missing translations
/blog/hreflang-basics — missing de (have en, fr)
missing-back-link https://example.com/fr/blog/hreflang-basics
/fr/blog/hreflang-basics declares an alternate to /de/... but the peer does not link back.
SEO issues
https://example.com/blog/hreflang-basics
warn canonical.missing Page is missing <link rel="canonical">.
warn description.length Description is 31 characters — short of the recommended 50–160 window.
https://example.com/pricing
error robots.conflict Page asks to be indexed while robots.txt disallows it.
warn og.image.missing Page has no og:image.
Site-wide issues
locales: en, fr, de (via sitemap)
https://example.com/
error hreflang.missing Site serves 3 locales but declares no hreflang alternates.
note: 1 link returned 403 from an anti-bot filter, not counted as broken.
Real output; only the spacing is adjusted for the page. Progress goes to stderr so stdout stays clean for the report.
Gate on regressions, not on perfection
A run that fails on any finding is unusable on a site that is not clean yet, so it gets switched off, or quietly ignored. Three flags make the gate tenable, and one of them makes the backlog actually shrink.
Capture a baseline
goflag https://example.com \ --baseline .goflag/baseline.json --update-baselineFreeze today’s findings as known, so the gate can ask whether this change made things worse instead of whether the site is perfect. The output never says clean while findings are outstanding; it says how many are being let through.
Gate on what is new
goflag http://localhost:3000 --no-external \ --baseline .goflag/baseline.json --regressions-onlyFindings are matched by fingerprint, and page URLs are normalised to origin-independent routes, so a baseline captured against production compares cleanly with a run against localhost.
Lower the ratchet
goflag … --baseline .goflag/baseline.json --max-debt 13Fix a finding, drop --max-debt by one, commit both. Without this, a baseline fossilises behind a build that passes.
In CI
Two moments are worth auditing, and they answer different questions.
| When | Against | Answers |
|---|---|---|
| On the merge request | The branch’s own build, booted by --start | Does this change regress? |
| After deploying | The running environment | Is what is actually serving correct? |
The flags worth knowing (--no-external, version pinning, exit codes) are three short sections in the docs.
See the CI recipesBuilt by auditing my own sites
goflag exists because four sites I run needed it, this one included: goflag.tech is gated by its own audit in CI, and a merge that breaks it does not land. Every number below is measured, not estimated.
- sites gated in CI
- 4sites gated in CI
- pages audited in 4 min
- 456pages audited in 4 min
- tests in the CLI
- 516tests in the CLI
What goflag is not
One command, a JSON report, an exit code. If what you want is a number to optimise, this is the wrong tool: a score invites optimising the score.
If you want a desktop crawler with a UI, Screaming Frog exists; if you only want dead links, lychee is faster. goflag is for gating a build on what a crawler reads.
Before you install
Point it at your site.
The quickstart takes you from a red first run to a gate you trust.
npx @goflag/cli https://example.com