Skip to content

Check Links

Check Links #587

Workflow file for this run

name: Check Links
# Lychee is the EXTERNAL link checker. It makes real network requests and is
# flaky by nature. Site-internal routes (`/docs/...`) are a different problem
# with a different tool — `scripts/check-doc-links.mjs`, run by
# `docs-links.yml`, which reads the checkout and nothing else and is therefore
# the one that gates pull requests. The two are complementary, not redundant;
# do not move either one's job onto the other.
#
# SCAN SCOPE — stated as populations, never counted (objectui#7448,
# objectui#7825). What gets swept is decided by the `args` glob list below and
# by nothing else: the published documentation tree `content/docs/**` (the
# fumadocs content source `apps/site/source.config.ts` declares —
# `dir: '../../content/docs'`, baseUrl `/docs`), the repo-root INTERNAL tree
# `docs/**` (ADRs, audits), and `README.md`.
#
# ⛔ Never write the size of either tree back into this comment. Two hand-copied
# sizes stood here, one per tree, and both had drifted by the time anyone
# looked, with nothing red over the whole distance — nothing fails on a stale
# number written in a comment, which is exactly why it rots. Point at the
# reading instead: `find content/docs docs -type f \( -name '*.md' -o -name
# '*.mdx' \) | wc -l` in any checkout, and Lychee's own run summary, which
# reports what it actually scanned. `check-links-workflow.test.ts` enforces
# this — it reds on the next numeral that qualifies a document population
# anywhere in this header.
#
# HISTORY, past tense on purpose — none of the following describes the scope
# today. Until PR #3449 the `args` list named only the repo-root `docs/**`, so
# not one published page had ever been scanned: this workflow was green because
# of what it was not looking at. #3449 pointed it at both trees, and
# `scripts/__tests__/check-links-workflow.test.ts` derives the expected scope
# from the site config rather than restating it, so moving the content tree
# turns that test red instead of blinding the sweep a second time.
on:
workflow_dispatch:
# Weekly sweep, Sundays at 04:17 UTC.
#
# The PR that fixed #3213 deliberately did NOT add a cron, and it was right to
# hold off: with the scope pointed at the wrong tree, a schedule only turns
# one false-green report into a periodically produced false-green report. Now
# that the scope is correct, #3213's ruling-B intent — a periodic sweep — is
# real, so the schedule lands in the same change that makes it meaningful.
#
# Timing: off the top of the hour, because GitHub's scheduled-workflow queue
# is most congested (and most delayed) at :00; Sunday because that is when the
# repo contends least for runners.
schedule:
- cron: '17 4 * * 0'
# ⛔ Do NOT enable the two triggers below — #3213's ruling B still stands.
# An external link check goes over the network: one 502, rate-limit or
# anti-scraping response from a third-party site turns an unrelated pull
# request red, and its author can do nothing about it. `schedule` +
# `workflow_dispatch` do not have that problem: this workflow blocks nobody,
# so flakiness costs a second look and nothing else.
# push:
# branches:
# - main
# pull_request:
jobs:
check-links:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Check links with Lychee
uses: lycheeverse/lychee-action@v2
with:
# Both trees:
# content/docs/** — the published site's documentation (the fumadocs
# content source). Never scanned before #3449; the whole point.
# docs/**, README.md — the repo-root internal material (ADRs,
# audits). Their external links are worth sweeping too and cost
# nothing extra, so they stay.
# Site-absolute routes (`/docs/...`) are NOT judged here — see the
# `root_dir` section of lychee.toml.
args: |
--verbose
--no-progress
--config lychee.toml
'content/docs/**/*.md'
'content/docs/**/*.mdx'
'docs/**/*.md'
'docs/**/*.mdx'
'README.md'
# Fail the action on broken links
fail: true
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}