11# ============================================================
22# .github/workflows/links.yml (Lychee Link Checker)
33# ============================================================
4- # SOURCE: https://github.com/denisecase/templates
5- #
4+ # Updated: 2026-04-13
5+
66# WHY-FILE: Automated link checking.
77# OBS: Behavior is configured in lychee.toml in this repository.
88# OBS: Runs on pull requests and monthly on schedule; manual trigger always available.
99
1010name : Check Links
1111
1212on :
13+ workflow_call : # WHY: Allow this workflow to be called by other workflows if needed
1314 workflow_dispatch : # WHY: Manual trigger - always available
1415 pull_request : # WHY: Validates PR links before merge
1516 schedule :
1617 - cron : " 0 6 1 * *" # WHY: Runs monthly (1st of month)
1718
1819concurrency :
1920 # WHY: Prevent multiple simultaneous link checks on same ref
20- group : link-check-${{ github.ref }}
21+ group : link-check-${{ github.ref || github.run_id }}
2122 cancel-in-progress : true
2223
2324permissions :
2425 contents : read # WHY: Needed to checkout code.
25- issues : write # WHY: Needed to create issue on scheduled failures.
26- pull-requests : write # WHY: Needed to comment on PR if links broken.
2726
2827env :
2928 PYTHONUNBUFFERED : " 1" # WHY: Real-time logging.
3029 PYTHONIOENCODING : " utf-8" # WHY: Ensure UTF-8 encoding for international characters.
31- REPORT_FAILURES : " true " # WHY: Enable PR comments and scheduled issues for link failures .
30+ REPORT_PATH : " ./lychee/out.md " # WHY: Predictable markdown report path for summary generation .
3231
3332jobs :
3433 lychee :
@@ -39,69 +38,16 @@ jobs:
3938 steps :
4039 - name : 1) Checkout repository code
4140 uses : actions/checkout@v6
41+ # WHY: Required so Lychee can inspect repository files.
4242
43- - name : 2) Check links with Lychee
44- id : lychee
43+ - name : 2) Run Lychee
4544 uses : lycheeverse/lychee-action@v2.8.0
4645 with :
47- fail : true # WHY: Fail the step if broken links found
4846 args : >
49- --config lychee.toml
50- --user-agent "${{ github.repository }}/lychee"
51- './**/*.bib'
47+ --config .github/ lychee.toml
48+ --verbose
49+ --no-progress
5250 './**/*.md'
5351 './**/*.html'
54- './**/*.tex'
5552 './**/*.yml'
5653 './**/*.yaml'
57- env :
58- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
59-
60- - name : 3) Comment on PR if links broken
61- if : failure() && github.event_name == 'pull_request' && env.REPORT_FAILURES == 'true'
62- uses : actions/github-script@v9
63- with :
64- script : |
65- const runUrl = `${context.payload.repository.html_url}/actions/runs/${context.runId}`;
66- const comment = [
67- "## Link Check Results",
68- "",
69- `Some links appear broken. Check the workflow logs: ${runUrl}`,
70- ].join("\n");
71-
72- await github.rest.issues.createComment({
73- issue_number: context.issue.number,
74- owner: context.repo.owner,
75- repo: context.repo.repo,
76- body: comment,
77- });
78-
79- - name : 4) Create issue for scheduled failures
80- # WHY: Track broken links found during scheduled checks
81- # OBS: Only creates issue if no open issue from github-actions bot exists
82- if : failure() && github.event_name == 'schedule' && env.REPORT_FAILURES == 'true'
83- uses : actions/github-script@v9
84- with :
85- script : |
86- const owner = context.repo.owner;
87- const repo = context.repo.repo;
88- const date = new Date().toISOString().split("T")[0];
89- const title = `Link Check Failed - ${date}`;
90- const runUrl = `${context.payload.repository.html_url}/actions/runs/${context.runId}`;
91- const body = `Monthly link check found broken links.\n\nWorkflow logs: ${runUrl}`;
92-
93- const existing = await github.rest.issues.listForRepo({
94- owner: owner,
95- repo: repo,
96- state: "open",
97- creator: "github-actions[bot]",
98- per_page: 1,
99- });
100-
101- const alreadyOpen = existing.data.some(
102- (i) => typeof i.title === "string" && i.title.startsWith("Link Check Failed -")
103- );
104-
105- if (!alreadyOpen) {
106- await github.rest.issues.create({ owner, repo, title, body });
107- }
0 commit comments