Skip to content

DX-2361: Add GitHub Actions to validate external URLs and internal anchor fragments - #1865

Merged
sharadregoti merged 8 commits into
mainfrom
DX-2361-link-and-anchor-validation
Apr 11, 2026
Merged

DX-2361: Add GitHub Actions to validate external URLs and internal anchor fragments#1865
sharadregoti merged 8 commits into
mainfrom
DX-2361-link-and-anchor-validation

Conversation

@sharadregoti

@sharadregoti sharadregoti commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Jira Ticket

DX-2361

Summary

Two link-correctness gaps existed in the docs CI — this PR closes both:

  • External URL checking was already implemented in the script but never enabled in CI
  • Internal anchor fragments (/page#section) were checked for file existence but the #section part was silently ignored — 371 broken anchors exist across 108 files today

Changes

scripts/validate_mintlify_docs.py

  • Added slugify_heading() — converts heading text to GFM anchor slug (Mintlify's format)
  • Added extract_file_anchors() — collects all valid anchors from an MDX file:
    • ATX headings (## Heading) → GFM slug
    • Custom heading IDs (## Heading {#custom-id})
    • HTML <a id="..."> and <a name> elements
  • Added build_anchor_map() — pre-builds a file→anchors cache across all MDX files
  • Added find_internal_links_with_anchors() — extracts (path, fragment) pairs from links
  • Added check_broken_anchors() — validates each fragment against the target file's anchor set
  • Added --check-anchors CLI flag; all existing behaviour is unchanged when flag is absent

.github/workflows/validate-docs.yml

  • Added a "Check anchor fragments" step that runs on every PR

.github/workflows/check-external-links.yml (new)

  • Runs weekly on Mondays at 07:00 UTC and on manual trigger (workflow_dispatch)
  • Uses existing --external-links flag; reports only failures (--external-errors-only)
  • Configurable timeout and delay via workflow_dispatch inputs

Known Limitation

Files that use MDX import to pull in snippets (e.g. tyk-oss-gateway/configuration.mdx) may show false-positive anchor failures because the checker reads the file directly, not its imported snippets. These will need to be triaged separately.

Test Plan

  • Run python scripts/validate_mintlify_docs.py . --check-anchors --links-only locally to confirm anchor failures are reported correctly
  • Verify validate-docs.yml check step passes on a PR with valid anchors
  • Trigger check-external-links.yml manually via Actions → Run workflow

…chors

- Extend validate_mintlify_docs.py with --check-anchors flag that extracts
  GFM-slugified heading anchors, {#custom-id} syntax, and <a id/name> elements
  from target MDX files and verifies every internal #fragment resolves
- Update validate-docs.yml to run anchor check on every PR
- Add check-external-links.yml workflow: weekly scheduled (Mon 07:00 UTC) and
  manual-trigger run that HEAD-checks all external HTTP/HTTPS URLs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Failed to generate code suggestions for PR

sharadregoti and others added 7 commits April 11, 2026 12:50
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
External link failures were reported but never factored into the
exit code, so CI was passing despite 404s. Added has_broken_external
check and a summary line for external link results.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace sequential Python/requests approach with lycheeverse/lychee-action.
Lychee runs checks in parallel, has built-in retry logic, and is purpose-built
for link checking — much faster on a repo with 1500+ external URLs.

Add lychee.toml to exclude tyk-owned domains, localhost, placeholder URLs,
and sites known to block bots (LinkedIn, Facebook). Accept 429 as non-broken
to handle rate-limited responses gracefully.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GitHub Actions expands ${{ }} expressions before the shell runs, so
backticks and $ signs in PR bodies were injected raw into the script
and interpreted as shell command substitution.

Fix: write static content via a single-quoted heredoc (no shell
expansion), then append the PR body via printf with an env var
($PR_BODY). Shell variables accessed as "$VAR" are never
re-interpreted, making any PR body content safe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add scheme = ["https", "http"] to lychee.toml so lychee only attempts
to check http/https URLs. Without this, lychee tries to resolve
root-relative paths (/img/..., /page/...) as file URIs and fails.
Internal links are already validated by validate-docs.yml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scheme = ["https", "http"] didn't help because lychee fails to build
the URL before the scheme filter runs. Set base = "https://tyk.io" so
root-relative paths (/img/..., /page/...) are resolved to
https://tyk.io/... and then silently skipped by the existing tyk.io
exclude rule, rather than producing "cannot convert path to URI" errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sharadregoti
sharadregoti merged commit 88b1aa0 into main Apr 11, 2026
2 of 4 checks passed
@sharadregoti
sharadregoti deleted the DX-2361-link-and-anchor-validation branch April 11, 2026 09:37
sharadregoti added a commit that referenced this pull request Apr 11, 2026
Fixes all broken anchor fragments identified by the CI anchor checker
introduced in DX-2361 (#1865).

Root causes fixed:
- Release note anchors: old slugify removed dots from version numbers
  (e.g. 5.3.0 → #530-release-notes); updated to dot-to-hyphen format
  (#5-3-0-release-notes) across gateway, dashboard, helm-chart, operator,
  portal, and archived release note pages
- Restructured pages: logs-metrics, portal/install, and tyk-self-managed/install
  were reorganised; removed or updated stale anchors to current headings
- Moved content: JWT signature validation content moved to dedicated page;
  updated links from json-web-tokens to jwt-signature-validation
- Wrong anchor names: fixed typos and outdated anchors in implement-tls,
  certificates, mdcb, graphql, dynamic-client-registration, dashboard-config
- Mintlify slug nuance: headings with ": " generate double hyphens (--);
  fixed portal step links and other colon-containing headings
- Validator: updated heading regex to detect headings inside ordered list
  items (pattern used in troubleshooting-debugging.mdx)

Result: 0 broken anchor fragments (was 163 across 60 files)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sharadregoti

Copy link
Copy Markdown
Contributor Author

/release to release-5.12

buger pushed a commit that referenced this pull request Apr 11, 2026
…chor fragments (#1865)

* DX-2361: Add GitHub Actions to validate external URLs and internal anchors

- Extend validate_mintlify_docs.py with --check-anchors flag that extracts
  GFM-slugified heading anchors, {#custom-id} syntax, and <a id/name> elements
  from target MDX files and verifies every internal #fragment resolves
- Update validate-docs.yml to run anchor check on every PR
- Add check-external-links.yml workflow: weekly scheduled (Mon 07:00 UTC) and
  manual-trigger run that HEAD-checks all external HTTP/HTTPS URLs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* DX-2361: Run external link check on every pull request

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* DX-2361: Fix exit code not failing on broken external links

External link failures were reported but never factored into the
exit code, so CI was passing despite 404s. Added has_broken_external
check and a summary line for external link results.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* DX-2361: Switch external link checker to lychee

Replace sequential Python/requests approach with lycheeverse/lychee-action.
Lychee runs checks in parallel, has built-in retry logic, and is purpose-built
for link checking — much faster on a repo with 1500+ external URLs.

Add lychee.toml to exclude tyk-owned domains, localhost, placeholder URLs,
and sites known to block bots (LinkedIn, Facebook). Accept 429 as non-broken
to handle rate-limited responses gracefully.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix mirror-pr workflow failing on PR bodies with special characters

GitHub Actions expands ${{ }} expressions before the shell runs, so
backticks and $ signs in PR bodies were injected raw into the script
and interpreted as shell command substitution.

Fix: write static content via a single-quoted heredoc (no shell
expansion), then append the PR body via printf with an env var
($PR_BODY). Shell variables accessed as "$VAR" are never
re-interpreted, making any PR body content safe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix lychee checking internal/relative links

Add scheme = ["https", "http"] to lychee.toml so lychee only attempts
to check http/https URLs. Without this, lychee tries to resolve
root-relative paths (/img/..., /page/...) as file URIs and fails.
Internal links are already validated by validate-docs.yml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix lychee erroring on root-relative paths

scheme = ["https", "http"] didn't help because lychee fails to build
the URL before the scheme filter runs. Set base = "https://tyk.io" so
root-relative paths (/img/..., /page/...) are resolved to
https://tyk.io/... and then silently skipped by the existing tyk.io
exclude rule, rather than producing "cannot convert path to URI" errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix lychee config: base -> base_url

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit 88b1aa0)
@github-actions

Copy link
Copy Markdown

✅ Cherry-pick successful. A PR was created and auto-merged (if allowed): #1868

sharadregoti pushed a commit that referenced this pull request Apr 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant