ci(docs): stop failing the weekly link pass on a transient upstream status - #199
Conversation
…tatus
The 2026-09-07 scheduled run failed with three 504 Gateway Timeouts from
github.com, all of them this repo's own pages: the `compare/1.5.0...HEAD` link
and the 1.3.0 and 1.4.1 tag links, every one of which loads fine in a browser.
Neither of the two obvious explanations survives a look at the source. It is
not a missing token: `lychee-action`'s `action.yml` defaults its `token` input
to `${{ github.token }}` and `entrypoint.sh` exports it as `GITHUB_TOKEN`, so
lychee is already authenticated, and a token addresses rate limiting anyway,
which arrives as 403 or 429. It is not a missing retry either: lychee's
`--max-retries` defaults to 3.
So the job was failing on the one thing it does not exist to catch. It hunts
link ROT, and a 5xx says the host is up and unhappy rather than that the link
is gone. `--accept` now widens the default `100..=103,200..=299` with 429 and
`500..=504`; 404 and 410 still fail the job, which is what rot looks like.
The cache is the second half. A same-day re-run reuses the answers instead of
asking every host again, and the 1 day age is deliberately shorter than the
weekly cadence, so each Monday pass still checks every URL for real.
Verified: `actionlint` clean, `zizmor` clean, and the workflow dispatched on
this branch.
|
Warning Review limit reachedNext included review available in 1 minute. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (2)
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe documentation link-check workflow now restores a one-day Lychee cache keyed by commit SHA. Lychee uses the cache and accepts HTTP 429 and 500–504 responses while continuing to fail on missing-link statuses such as 404 and 410. ChangesDocs link checking
Estimated code review effort: 1 (Trivial) | ~5 minutes Merge Risk: ⚪ Minimal · up to The weekly external-link check will tolerate transient upstream rate-limit and server errors while retaining failures for missing links. The caching and response-policy change is ready to merge. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Proved the flag rather than trusting it. The dispatched run passes (939 links, 935 OK, 4 excluded, 0 errors), but GitHub answered every request that time, so that run only shows the workflow is well-formed. So I pulled the exact binary CI installs, Defaults, which is what run 34130370451 hit: With the accept list this PR adds: So the 504 and the 429 stop failing the job and the 404 still fails it, which is the whole point: rot is still caught. The cache half checks out too. A first |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Note Kodizm (AI-generated). May contain mistakes; verify before acting. The diagnosis is right and the flags parse and behave as claimed, but the cache does not save on the run it was added for, and the CHANGELOG entry this repo gives workflow-only changes is missing. Major
Minor
TestsNo test surface for a workflow file. Verified instead by running the pinned lychee binary with the exact flag string from the diff. Checks I ran
|
Two review findings, both verified against the source before acting. The cache does not save on the run it was added for. `actions/cache` declares `post-if: "success()"` at the pinned SHA, so the save step is skipped whenever the job fails, which after this change is exactly the run a retry would follow: a 429 or a 5xx no longer fails, so a red run means a real 404. Splitting into `restore` plus `save` with `if: always()` would fix that, but the whole external pass costs under four seconds for 939 links (run 34150081246), so there is nothing worth buying back. Dropping it also retires three smaller notes in one move: `--max-cache-age 1d` was restating lychee's own default, an accepted 429 or 504 would have been replayed out of the cache for the rest of the day, and `.lycheecache` would have needed a `.gitignore` line for anyone reproducing the pass locally. The CHANGELOG entry was missing. `CLAUDE.md` files CI infrastructure under `### Quality`, and 1.4.1 carries four workflow-only entries there. This change alters when a job goes red, which is behaviour. Verified again after the edit: `actionlint` clean, `zizmor` clean, `tool/check-docs.py` 0 issues.
|
Both findings verified against the source and taken in caff7e3, and the cache is gone rather than repaired.
The CHANGELOG entry is in under What is left in the diff is one flag and its comment. Re-verified after the edit: |
|
Note Kodizm (AI-generated). May contain mistakes; verify before acting. Incremental re-review of Since my last pass: the Minor
TestsNo test surface for a workflow file. I re-parsed the YAML and reproduced the docs gate instead; the accept-list behaviour was already measured against the pinned lychee 0.24.2 in the thread above and I am not re-litigating it. Checks I ran
|
The review caught the comment claiming "the 5xx range" over a flag that read 500..=504, and the code is what was wrong. Stopping at 504 covers the failure that prompted this change and leaves the same class failing one door down: Cloudflare answers 520, 521, 524 and 530 for an origin that did not respond, and 508 sits outside the range too. Measured on the pinned lychee 0.24.2 over 503, 504, 508, 521, 524, 530 and a 404. The narrow range rejects five of the seven, the widened one rejects the 404 alone, which is the only link in the list that is actually gone. `actionlint` clean, `zizmor` clean, `tool/check-docs.py` 0 issues.
|
Right, and the code was the wrong half. Fixed in c296a51 by widening the range rather than narrowing the sentence: stopping at 504 covers the failure that prompted this and leaves the same class failing one door down, since Cloudflare answers 520, 521, 524 and 530 for an origin that did not respond, and 508 sits outside it too. Measured on the pinned lychee 0.24.2 over 503, 504, 508, 521, 524, 530 and a 404: The 404 is the only link in that list that is actually gone, and it is the only one that still fails the job. The comment and the CHANGELOG entry both carry the reason for the wider bound now.
|
|
Note Kodizm (AI-generated). May contain mistakes; verify before acting. Incremental re-review of Since Worth stating plainly since it is the whole risk surface of the widening: TestsNo test surface for a workflow file. The accept-list behaviour was measured against the pinned lychee 0.24.2 in the thread above, including the 503/508/521/524/530 probe; I am not re-litigating it. Checks I ran
|
What
The weekly
External Linkspass no longer fails on a 429 or a 5xx, and it now keeps a one-day request cache.Plus an
actions/cacherestore step for.lycheecache, pinned by SHA like every other action here.Why
Run 34130370451 failed with three 504 Gateway Timeouts from github.com, all of them this repo's own pages:
All three load fine in a browser, and neither obvious explanation survives a look at the source:
lychee-action'saction.ymldefaults itstokeninput to${{ github.token }}andentrypoint.shexports it asGITHUB_TOKEN, so lychee is already authenticated. A token addresses rate limiting anyway, which arrives as 403 or 429, not 504.--max-retriesalready defaults to 3, with a 1 second wait.So the job was failing on the one thing it does not exist to catch. It hunts link rot, and a 5xx says the host is up and unhappy rather than that the link is gone. 404 and 410 still fail the job, which is what rot actually looks like.
The cache is the second half: a same-day re-run reuses the answers instead of asking every host again. The one-day age is deliberately shorter than the weekly cadence, so each Monday pass still checks every URL for real and the cache only helps the manual retry after a transient failure.
Testing
actionlint .github/workflows/docs-link-check.yml: cleanzizmor .github/workflows/docs-link-check.yml: no findingsNot included
--accept-timeoutsstays off. A client-side timeout is not what failed here, and swallowing one would hide a host that hangs rather than answers. It is the next knob if a timeout ever shows up.Summary by CodeRabbit