# Lychee link checker config — used by .github/workflows/docs-quality.yml exclude_path = [ ".claude/worktrees", "node_modules", ".git", "packages/docs/dist", "packages/docs/.astro", ] exclude = [ # Asciinema casts get uploaded post-merge; skip until the ID exists "asciinema\\.org/a/", # localhost references in code examples "localhost", "127\\.0\\.0\\.1", # GitHub Releases anchors that are page-internal "github\\.com/.*/releases/tag", # release-please CHANGELOG compare links — the higher tag is only created # by the release job AFTER the release PR merges, so the link 404s while # the PR is open. Resolves automatically post-merge. "github\\.com/nimbus-agent/Nimbus/compare/v", # External hosts that block scripted user-agents or rate-limit aggressively # from GitHub-hosted runner egress. Verified live in browsers; the failures # are infrastructure noise, not broken links. "npmjs\\.com", # 403 to non-browser UAs "canva\\.com", # 403 to non-browser UAs "opengraph\\.xyz", # aggressive 429 rate limit "tauri\\.app", # intermittent connection failures from runners "conventionalcommits\\.org", # intermittent TCP RST from GH runner egress "asciinema\\.org/?$", # homepage times out from runner egress; verified live (200) "asciinema\\.org/a/", # cast pages also time out under load from runner egress; verified live (200) "nimbus-agent\\.dev", # exclude until PR 1 DNS propagates "glama\\.ai", # 525/403 to scripted UAs due to Cloudflare # 403 from GitHub runner egress only. Checked rather than assumed: from a dev # machine it returns 200 to BOTH a browser UA and curl's default UA, so this is # origin/IP-based blocking, not the scripted-UA kind the entries above describe. # Pre-existing link (the Ramp connector CHANGELOG entry), unrelated to the PR # that had to exclude it. "ramp\\.com", # github-action-benchmark dashboard on the perf-data orphan branch — the branch # and dev/bench dir are created by the first post-merge push, so the link 404s # while the PR documenting it is open. Resolves automatically post-merge. "github\\.com/nimbus-agent/Nimbus/tree/perf-data", # open-vsx.org's signed-in settings pages (cited in docs/ci-secrets.md and # docs/credential-hygiene.md) answer 503 intermittently — measured 2026-09-17 from a # dev machine, not only runner egress: 3 of 6 samples 503 on `/user-settings/tokens` # while `/extension/...` pages returned 200 on all 6. lychee does not retry a # rejected status, so both passes failed in three runs across two PRs that day. Scoped to # `/user-settings/` so the public open-vsx.org links stay checked. "open-vsx\\.org/user-settings/", ] # 202 Accepted is a 2xx success — figma.com's homepage returns it to scripted UAs, # which lychee otherwise rejects (the long-standing intermittent figma.com flake). accept = ["200", "202", "203", "204", "206", "301", "302", "304"] max_redirects = 5 timeout = 30 # CORRECTION (supersedes the claim this comment used to make): `max_retries` and # `retry_wait_time` DO NOT apply to a rejected status code. Measured against a local # server that counts requests — a 504 and a 503 were each fetched exactly ONCE, while # a dropped connection was fetched FOUR times (1 + 3 retries). lychee retries # TRANSPORT errors only; a response that arrives and fails the `accept` list is final. # # So these two settings do nothing for the recurring github.com 504, which was the # reason given for adding `retry_wait_time` in #1200. They are kept because they are # still correct for genuine transport flakes (connection reset, TLS hiccup), which do # occur from runner egress — but they are not the 504 mitigation. # # The 504 is handled where it can be: `scripts/check-links.sh` (used by both # `audit:links` and the workflow step) caches successes with `--cache` and retries the # run once, so the retry re-checks ONLY what failed — verified: 2 OK URLs fetched once # across two passes, the 504 URL fetched twice. A genuinely dead link still fails both # passes and the gate still goes red, which accepting 5xx as success would not # preserve. max_retries = 3 retry_wait_time = 2 # Per-HOST, not global: almost every link here is github.com, so the run was # opening far more simultaneous connections to one host than it needs to. This # caps that without throttling other hosts. # # It is not free. Measured locally over the real link set (1,313 links, 845 # unique), one run each: 111s before, 127s after — roughly +15%, some of which # is network variance. That is judged worth it against a gate that has produced # two false failures in a day, each costing a re-run and eroding trust in the # job. Raise this number if the run time starts to matter more than the flake. host_concurrency = 8