Link checking
The site is link-checked with Lychee, backed by a committed cache of external-link results (see Link cache).
CI link-checks every PR, and the bot can update the link cache
for you. To run checks locally, install Lychee; CI installs
its own pinned copy (see the .github/actions/install-lychee action), so keep
your local version reasonably close to it.
Check links
To check links locally, run:
npm run check:links
Common commands
| Command | Checking scope |
|---|---|
check:links | Whole site |
check:links:internal | Whole site, offline (no external links) |
check:links:diff | Changed files only |
fix:link-cache | Alias of check:links; use it to refresh the link cache |
The check:links and check:links:internal scripts run over a build of
BUILD_KIND; check:links:diff checks files from the existing public/ build.
For details, see Build kinds: full and lean.
Configuration
Lychee runs over the built site (public/) using the generated, git-ignored
lychee.toml. The generate:config:links script derives it from
lychee.base.toml plus an exclude_path block computed from page front
matter, which has two sources:
link_check_exclude_path— a list of site-relative path regexes for pages the link checker must skip, such as blog pagination and old blog posts; seecontent/en/blog/_index.md. Start a pattern with^(../)?to have it cover every locale: the optional../matches a two-letter locale path segment such asja/.drifted_from_default— drifted localized pages, statustrue(EN counterpart changed) orfile not found(EN counterpart deleted). Links from such a page aren’t checked, since they may be stale, but the page remains a valid link target: inbound links from in-sync pages, including fragments, are still validated.
Stored drift statuses are only as fresh as the last nightly Housekeeping
status sync (as merged, so the window can exceed a day), so the generator also
skips drift-pending pages: locale copies of English pages changed (or
deleted) since the drift-status baseline, the main-branch commit recorded in
data/l10n-drift.yaml by tree-wide status syncs (npm run fix:i18n). A copy
that itself changed since the baseline stays checked: someone is working on it.
Config generation fails when the baseline is missing or can’t be resolved; in
CI, the CHECK LINKS job first deepens its shallow clone to the baseline
commit; locally, fetch the missing history (git fetch upstream main) or
override the baseline: DRIFT_BASELINE=HEAD npm run check:links empties the
overlay (stored-status skips still apply).
A local tree-wide status sync (npm run fix:i18n) can rewrite
data/l10n-drift.yaml; leave that rewrite uncommitted — a locally recorded
commit might not exist upstream.
Link cache
External-link check results are cached in .lycheecache, which is under version
control so that checks only fetch URLs that are new or whose cache entries have
expired. Lychee caches successful results only, so failures are retried on every
run.
Since the cache is routinely updated by several
scheduled workflows as well as content PRs, concurrent updates are
merged line-by-line with Git’s union strategy (see .gitattributes)
rather than reported as conflicts. Such merges can leave duplicate or stale
entries behind; these are benign for the checker, and the next link-check run
rewrites the cache clean. In a PR, commit that rewrite.
If you add or change external links, run npm run check:links before
submitting your PR — the site build dominates the run time — and commit the
updated .lycheecache along with your content changes. Otherwise the
CACHE updates committed? check will fail; for recovery steps, see
CACHE updates committed?.
Cache refresh and housekeeping workflows
The following workflows are scheduled daily and run a link checking command:
| Workflow | Link-check command |
|---|---|
| Refcache refresh | log:check:links (full build, after pruning) |
Housekeeping (fix-and-test:all) | fix:link-cache (full build) |
| Auto-update registry versions | fix:link-cache |
Refcache refresh prunes the oldest cache entries (the count is a workflow input) and re-runs the link check, which refreshes the cache entries for the pruned URLs that are still used in the site.
Double-check of failing links
Some sites serve valid pages to browsers but turn away plain HTTP clients like Lychee (bot walls, crates.io’s unconditional 404s, npmjs.com signin redirects). Since failures are never cached, links to such sites would otherwise fail the link check on every run once their cache entries expire.
The double-check tooling re-verifies Lychee-reported failures through a
browser-grade probe. URLs that the probe resolves are recorded in .lycheecache
with the synthetic status 206 (“OK by analysis”). The Refcache refresh
workflow runs it after the link check; to run it locally over a captured log:
npm run log:check:links
npm run fix:link-cache:double-check
For options, run npm run fix:link-cache:double-check -- --help. For probe
behavior and setup, see the double-check README.
In CI
The check-links.yml workflow builds the site once (lean) and shares that
artifact with the CHECK LINKS job, so local runs and CI check the same build.
That job fails if any link check fails, and hands the cache it refreshed to the
CACHE updates committed? job, which fails if the run left the committed
.lycheecache stale.