Repository navigation
Add the offline link gate to the 18 green repos, and unpublish the ADRs in the two that have them #66
Description
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 inREADME.md
anddocs/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 inplanning/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-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 a
spurious "Cannot find fragment" for a link that works on the site. Confirmed by running
markdown.extensions.toc.slugifyagainst the three headings involved.- Not a reusable workflow. Copied into each repo, matching how the org already shares
_checks.ymlandreport-scheduled-failure.sh. The config is 5 static lines with no
parameters, so there is nothing to drift.
Acceptance criteria
- The 18 repos each fail CI on a PR that introduces a broken relative link.
- No repo's gate is red on adoption.
-
modern-diandfaststream-outboxno longer serve/adr/on their docs sites, and
mkdocs build --strictstill passes in both. -
chat-appis either adopted in whatever shape suitsmain.yml, or explicitly skipped.
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'slinks.yml— separate, tracked on its own.
Activity
ci: gate PRs on local links; stop publishing the ADRs (#454)
ci: gate PRs on local links; stop publishing the ADRs (#165)
lesnik512 commentedon Sep 6, 2026
Pilot merged — both repos that carry ADRs are done:
modern-di— ci: gate PRs on local links; stop publishing the ADRs modern-di#454, 10/10 checks green; thelinksjob processed 565 links, 0 errors.faststream-outbox— ci: gate PRs on local links; stop publishing the ADRs faststream-outbox#165, 9/9 green; 241 links, 0 errors.Both CI runs matched their local baselines exactly, so the gate is verified as doing real work rather than passing empty.
Remaining: the offline gate in the other 16 repos. No
mkdocs.ymlchange is needed in any of them — neither ADR-related acceptance criterion applies, since only these two repos have ADRs. All 16 measured green, so none should need a fix.chat-appis still the one open question in scope: it has only amain.ymland no_checks.ymlto hook into.chore: migrate off the planning/ convention (#40)
chore: migrate off the planning/ convention (#19)
chore: migrate off the planning/ convention (#14)
chore: migrate off the planning/ convention (#19)
chore: migrate off the planning/ convention (#13)
chore: migrate off the planning/ convention (#14)
chore: migrate off the planning/ convention (#42)
chore: migrate off the planning/ convention (#44)
chore: migrate off the planning/ convention (#14)
chore: migrate off the planning/ convention (#12)
chore: migrate off the planning/ convention (#14)
chore: migrate off the planning/ convention (#34)
21 remaining items
chore(ci): check absolute self-links in the offline link gate (#44)
chore(ci): check absolute self-links in the offline link gate (#15)
chore(ci): check absolute self-links in the offline link gate (#14)
chore(ci): check absolute self-links in the offline link gate (#45)
chore(ci): check absolute self-links in the offline link gate (#41)
chore(ci): check absolute self-links in the offline link gate (#20)
chore(ci): check absolute self-links in the offline link gate (#13)
chore(ci): check absolute self-links in the offline link gate (#38)
chore(ci): check absolute self-links in the offline link gate (#461)
chore(ci): check absolute self-links in the offline link gate (#83)
chore(ci): check absolute self-links in the offline link gate (#63)
chore(ci): check absolute self-links in the offline link gate
chore(ci): check absolute self-links in the offline link gate
chore(ci): check absolute self-links in the offline link gate (#27)
chore(ci): check absolute self-links in the offline link gate (#242)