Supersedes #64, which proposed sharing the external check as a cross-repo reusable
workflow. That was retired on measurement — see its closing comment. This is the half that
survived, and it needs no shared machinery.
What
Add a blocking, offline link check to each repo's existing _checks.yml, matching what
.github runs since #65:
links:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
# --offline blocks network requests and excludes every external URL, so this gate
# is deterministic: it fails only on a relative link or file path a diff broke.
- name: Check local links
uses: lycheeverse/lychee-action@v2
with:
args: >-
--offline
--no-progress
'**/*.md'
No exclusions, no token, no schedule, no cross-repo dependency. --offline blocks all
network requests and excludes external URLs rather than erroring on them, which is what
makes it safe to block on.
Why
A relative link breaks because a diff broke it, and today nothing catches that. mkdocs build --strict validates links inside docs/ only — root Markdown, .github/ and
docs/agents/ are unchecked in every repo.
Measured across all 28 non-archived repos with the lychee container:
- 77 broken local links org-wide, 77 of 77 inside
planning/, zero in any user-facing
surface. Checked against a synthetic repo with deliberately broken links in README.md
and docs/ to confirm the clean result is real and not a scan-scope artefact.
- 21 repos are green today. The other 7 (httpware 56, lite-bootstrap 8, compose2pod 4,
semvertag 3, faststream-concurrent-aiokafka 3, faststream-redis-timers 2, db-retry 1) are
broken only in planning/ history.
Scope: 18 repos
The 21 green repos, minus .github (already has it) and minus the two template repos
(fastapi-sqlalchemy-template, litestar-sqlalchemy-template — 3 Markdown files and one
checkable link each; the gate there is green but near-vacuous).
17 of the 18 follow the _checks.yml convention. chat-app has only a main.yml and
needs a different shape.
The 7 repos still carrying planning/ are deliberately not in scope. They go green as
a side effect of the convention migration that deletes planning/, so the gate should be
adopted there as part of it. Adding --exclude-path planning instead was considered and
rejected: an exclusion added for a transitional reason outlives the reason.
Also in scope: stop publishing the ADRs
Exactly two repos have ADRs — modern-di (27) and faststream-outbox (2) — and both carry
the identical not_in_nav: /adr/ block whose stated rationale was "built so their outgoing
links are validated". #65 retired that rationale here: the PR gate validates ADR links from
disk, so the ADRs move to exclude_docs and stop being publicly reachable.
Order matters: the gate lands first in those two repos, so link validation transfers
before --strict stops covering it. Both score 0 errors today, so both land green.
Decisions already taken
--include-fragments stays off. Tested: it found 0 real problems and 5 false ones.
lychee computes GitHub-flavoured heading slugs; MkDocs uses python-markdown's, which
collapse punctuation differently, so an em dash, & or backticks in a heading produce a
spurious "Cannot find fragment" for a link that works on the site. Confirmed by running
markdown.extensions.toc.slugify against the three headings involved.
- Not a reusable workflow. Copied into each repo, matching how the org already shares
_checks.yml and report-scheduled-failure.sh. The config is 5 static lines with no
parameters, so there is nothing to drift.
Acceptance criteria
Out of scope
- The external/weekly check in any repo but
.github.
- Fixing the 77 broken
planning/ links; the migration deletes them.
- The duplicate-issue defect in
.github's links.yml — separate, tracked on its own.
Supersedes #64, which proposed sharing the external check as a cross-repo reusable
workflow. That was retired on measurement — see its closing comment. This is the half that
survived, and it needs no shared machinery.
What
Add a blocking, offline link check to each repo's existing
_checks.yml, matching what.githubruns since #65:No exclusions, no token, no schedule, no cross-repo dependency.
--offlineblocks allnetwork requests and excludes external URLs rather than erroring on them, which is what
makes it safe to block on.
Why
A relative link breaks because a diff broke it, and today nothing catches that.
mkdocs build --strictvalidates links insidedocs/only — root Markdown,.github/anddocs/agents/are unchecked in every repo.Measured across all 28 non-archived repos with the lychee container:
planning/, zero in any user-facingsurface. Checked against a synthetic repo with deliberately broken links in
README.mdand
docs/to confirm the clean result is real and not a scan-scope artefact.semvertag 3, faststream-concurrent-aiokafka 3, faststream-redis-timers 2, db-retry 1) are
broken only in
planning/history.Scope: 18 repos
The 21 green repos, minus
.github(already has it) and minus the two template repos(
fastapi-sqlalchemy-template,litestar-sqlalchemy-template— 3 Markdown files and onecheckable link each; the gate there is green but near-vacuous).
17 of the 18 follow the
_checks.ymlconvention.chat-apphas only amain.ymlandneeds a different shape.
The 7 repos still carrying
planning/are deliberately not in scope. They go green asa side effect of the convention migration that deletes
planning/, so the gate should beadopted there as part of it. Adding
--exclude-path planninginstead was considered andrejected: an exclusion added for a transitional reason outlives the reason.
Also in scope: stop publishing the ADRs
Exactly two repos have ADRs —
modern-di(27) andfaststream-outbox(2) — and both carrythe identical
not_in_nav: /adr/block whose stated rationale was "built so their outgoinglinks are validated". #65 retired that rationale here: the PR gate validates ADR links from
disk, so the ADRs move to
exclude_docsand stop being publicly reachable.Order matters: the gate lands first in those two repos, so link validation transfers
before
--strictstops covering it. Both score 0 errors today, so both land green.Decisions already taken
--include-fragmentsstays off. Tested: it found 0 real problems and 5 false ones.lychee computes GitHub-flavoured heading slugs; MkDocs uses python-markdown's, which
collapse punctuation differently, so an em dash,
&or backticks in a heading produce aspurious "Cannot find fragment" for a link that works on the site. Confirmed by running
markdown.extensions.toc.slugifyagainst the three headings involved._checks.ymlandreport-scheduled-failure.sh. The config is 5 static lines with noparameters, so there is nothing to drift.
Acceptance criteria
modern-diandfaststream-outboxno longer serve/adr/on their docs sites, andmkdocs build --strictstill passes in both.chat-appis either adopted in whatever shape suitsmain.yml, or explicitly skipped.Out of scope
.github.planning/links; the migration deletes them..github'slinks.yml— separate, tracked on its own.