Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4aff3e2
ci: comment on PRs that break internal links or anchors
marcleblanc2 Sep 6, 2026
b42d4f0
test: rename a linked heading to exercise the PR comment (will be rev…
marcleblanc2 Sep 6, 2026
1c725f3
Revert "test: rename a linked heading to exercise the PR comment (wil…
marcleblanc2 Sep 6, 2026
11def83
check-links: accept <a name> and id= attributes as anchor targets
marcleblanc2 Sep 6, 2026
89a9e29
check-links: detect case mismatches in public/ and docs/ asset links too
marcleblanc2 Sep 6, 2026
228105f
check-links workflow: never comment on a clean PR
marcleblanc2 Sep 6, 2026
85d10d4
check-links workflow: say what the resolved comment actually means
marcleblanc2 Sep 6, 2026
6aefb81
check-links: strip fences line by line, first file wins a route, same…
marcleblanc2 Sep 6, 2026
72335cc
Add verify-links-live: prove a branch's changed links resolve on a de…
marcleblanc2 Sep 7, 2026
1574b85
check-links: slug the full heading text when a heading contains a lin…
marcleblanc2 Sep 7, 2026
35be99e
check-links: state that redirects do not satisfy the PR check
marcleblanc2 Sep 9, 2026
7bfe4e6
check-links: reword the inbound-link guidance in the PR comment
marcleblanc2 Sep 10, 2026
7ac4191
check-links: split the inbound-link guidance and say why redirects do…
marcleblanc2 Sep 10, 2026
29074eb
check-links: link each file path in the PR comment to the file on the…
marcleblanc2 Sep 10, 2026
5c3ac3a
check-links: install only github-slugger in CI, link line numbers to …
marcleblanc2 Sep 10, 2026
f83945e
check-links: split the PR comment into outbound and inbound broken links
marcleblanc2 Sep 10, 2026
41790f3
check-links: export listFiles and routeFor for check-redirects, cance…
marcleblanc2 Sep 10, 2026
c17d30e
check-links: flag absolute self-links with a suggested relative link,…
marcleblanc2 Sep 10, 2026
aba7bc5
check-links: review comment lists every finding on the line, naming t…
marcleblanc2 Sep 10, 2026
953692f
check-links: report absolute self-links only with --check-self-links,…
marcleblanc2 Sep 10, 2026
b3d025d
test: add absolute self-links and a dead external link to exercise th…
marcleblanc2 Sep 10, 2026
34db61d
test: re-run check-links to verify suggestions are not posted twice
marcleblanc2 Sep 10, 2026
67f39d4
test: more link shapes on one line
marcleblanc2 Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions .github/workflows/check-links.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
name: Check links

# Reports internal links and #anchors that this PR breaks, compared with the
# merge base, absolute links to this site, and external links on added lines
# that 404. Pre-existing broken links on the base branch are ignored.

on:
pull_request:

# A new push supersedes the run for the previous one
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

permissions:
contents: read
pull-requests: write

jobs:
check-links:
name: Broken links introduced by this PR
runs-on: ubuntu-latest
steps:
- name: Check out pull request head
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0

- name: Install github-slugger, the only dependency of dev/check-links.mjs
# Into a scratch prefix, not the repo: `npm install <pkg>` next to
# package.json would install every dependency of the site
run: |
npm install --prefix "$RUNNER_TEMP/deps" --no-package-lock --no-audit --no-fund \
"github-slugger@$(node -p 'require("./package.json").dependencies["github-slugger"]')"
ln -s "$RUNNER_TEMP/deps/node_modules" node_modules

- name: Check out merge base
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
merge_base=$(git merge-base "$BASE_SHA" HEAD)
git worktree add "$RUNNER_TEMP/base" "$merge_base"
git diff -U0 "$merge_base" HEAD > "$RUNNER_TEMP/changes.diff"

- name: Record broken links already present on the base branch
# Exit 1 means findings, which is expected here
run: |
node dev/check-links.mjs --check-anchors --check-self-links --format json \
--root "$RUNNER_TEMP/base" > "$RUNNER_TEMP/base-links.json" \
|| [ $? -eq 1 ]

- name: Find broken links introduced by this PR
id: check
env:
# File links in the report open the file on the PR branch
LINK_BASE: ${{ github.event.pull_request.head.repo.html_url }}/blob/${{ github.event.pull_request.head.ref }}
run: |
if node dev/check-links.mjs --check-anchors --check-self-links --check-external --format markdown \
--baseline "$RUNNER_TEMP/base-links.json" \
--diff "$RUNNER_TEMP/changes.diff" \
--review "$RUNNER_TEMP/review.json" \
--link-base "$LINK_BASE" > "$RUNNER_TEMP/report.md"; then
echo "broken=false" >> "$GITHUB_OUTPUT"
else
echo "broken=true" >> "$GITHUB_OUTPUT"
fi
cat "$RUNNER_TEMP/report.md"

- name: Comment on the pull request
# Fork PRs get a read-only token; the report is still in the job log
if: github.event.pull_request.head.repo.full_name == github.repository
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
BROKEN: ${{ steps.check.outputs.broken }}
run: |
marker='<!-- check-links-report -->'
existing_comment=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \
--paginate --jq ".[] | select(.body | startswith(\"$marker\")) | .id" | head -n 1)

# Comment only when there is something to report, or an earlier report to resolve
if [ "$BROKEN" = true ]; then
{ echo "$marker"; cat "$RUNNER_TEMP/report.md"; } > "$RUNNER_TEMP/comment.md"
elif [ -n "$existing_comment" ]; then
printf '%s\n### ✅ The broken links an earlier revision of this PR introduced are fixed\n' \
"$marker" > "$RUNNER_TEMP/comment.md"
else
exit 0
fi

if [ -n "$existing_comment" ]; then
gh api --method PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$existing_comment" \
--field body=@"$RUNNER_TEMP/comment.md"
else
gh pr comment "$PR_NUMBER" --body-file "$RUNNER_TEMP/comment.md"
fi

- name: Suggest fixes as review comments
# One suggested change per added line with a fix. Suggestions already on
# the PR (same file, line, and text) are not posted again.
if: steps.check.outputs.broken == 'true' && github.event.pull_request.head.repo.full_name == github.repository
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/comments" --paginate \
--jq '.[] | {path, line, body}' | jq -s . > "$RUNNER_TEMP/posted.json"
jq --slurpfile posted "$RUNNER_TEMP/posted.json" \
'.comments |= map(select(. as $comment | $posted[0] | index({path: $comment.path, line: $comment.line, body: $comment.body}) | not))' \
"$RUNNER_TEMP/review.json" > "$RUNNER_TEMP/review-new.json"

if [ "$(jq '.comments | length' "$RUNNER_TEMP/review-new.json")" -gt 0 ]; then
gh api --method POST "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews" \
--input "$RUNNER_TEMP/review-new.json" > /dev/null \
|| echo "::warning::Could not post the suggested fixes; they are in the report above"
fi

- name: Fail when this PR introduces broken links
if: steps.check.outputs.broken == 'true'
run: exit 1
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
- **Build**: `npm run build`
- **Dev**: `npm run dev`
- **Lint**: `npm run lint`
- **Check links**: `npm run check-links -- --check-anchors --check-self-links` (CI comments on PRs that break links; see `dev/check-links.mjs`; `next build` runs it without flags, so only dead page links fail a deploy). When moving a page or renaming a heading, update every link to it; a redirect in `src/data/redirects.ts` does not satisfy the check. Link to this site with relative paths (`/admin/config/site-config`), never `https://sourcegraph.com/docs/…` or `https://docs.sourcegraph.com/…`. To also probe the external links you added: `npm run check-links -- --check-anchors --check-self-links --check-external --diff <(git diff -U0 origin/main)`
- **Prove changed links resolve on a deploy**: `node dev/verify-links-live.mjs --site <vercel-preview-url>` prints a Markdown table for the PR description

## AI Chat Integration

Expand Down
Loading
Loading