← Files AMDARCHIVED FILE

.github/workflows/external-reference-check.yml

5.53 KB · Sep 30, 2026 · 23:13 UTC

↓ Download file

name: external-reference-check

# Reach out to every external URL referenced from our markdown and report
# anything that's unreachable. This is intentionally split from `validate`
# because external checks are flaky (transient 5xx, rate limits, runner IP
# blocks, DNS hiccups). The workflow fails on PR and manual runs so broken
# links are visible; whether they actually block merge is controlled by
# whether this check is marked required in branch protection (recommended:
# leave it not-required so a transient hiccup can't block a merge).
#
# Internal references and anchors are checked by `validate.yml` with
# `lychee --offline`.

on:
  pull_request:
    paths:
      - "**/*.md"
      - ".github/lychee.toml"
      - ".github/workflows/external-reference-check.yml"
  schedule:
    # Mondays at 12:00 UTC. Catches link rot in unchanged content.
    - cron: "0 12 * * 1"
  workflow_dispatch:
  # Lets OTHER repos reuse this exact check instead of copying it, so the
  # logic never drifts. A consumer adds a ~10-line caller workflow:
  #
  #   jobs:
  #     external-references:
  #       uses: amd/skills/.github/workflows/external-reference-check.yml@main
  #       permissions:
  #         contents: read
  #         issues: write
  #
  # The lychee config is NOT configurable: callers always use the canonical
  # .github/lychee.toml from amd/skills (pinned to the same commit as this
  # workflow), so neither the logic nor the config can drift.
  workflow_call:
    inputs:
      markdown_glob:
        description: "Glob of markdown files to check, relative to the caller repo root."
        type: string
        default: "./**/*.md"

permissions:
  contents: read
  issues: write

jobs:
  external-references:
    name: Check external references in skills
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v4

      # The lychee config always comes from amd/skills so it can never drift.
      # When this repo runs the check on itself, the working-tree copy is used
      # (so PRs that edit the config are tested against their own changes).
      # When another repo calls this workflow, the canonical config is fetched
      # from amd/skills at the SAME commit as this workflow file
      # (github.job_workflow_sha), keeping logic and config in lockstep.
      # Sparse-checkout grabs only the config file so its markdown can't
      # pollute the scan.
      - name: Fetch canonical lychee config
        if: ${{ github.repository != 'amd/skills' }}
        uses: actions/checkout@v4
        with:
          repository: amd/skills
          ref: ${{ github.job_workflow_sha || 'main' }}
          path: .external-reference-check-config
          sparse-checkout: |
            .github/lychee.toml
          sparse-checkout-cone-mode: false
          persist-credentials: false

      - name: Resolve lychee config path
        id: cfg
        shell: bash
        env:
          IS_CANONICAL: ${{ github.repository == 'amd/skills' }}
        run: |
          set -euo pipefail
          if [ "${IS_CANONICAL}" = "true" ]; then
            echo "path=.github/lychee.toml" >> "$GITHUB_OUTPUT"
          else
            echo "path=.external-reference-check-config/.github/lychee.toml" >> "$GITHUB_OUTPUT"
          fi

      - name: Check external references
        id: lychee
        uses: lycheeverse/lychee-action@v2
        with:
          args: --config ${{ steps.cfg.outputs.path }} --no-progress "${{ inputs.markdown_glob || './**/*.md' }}"
          # Fail the workflow on PR and manual runs so broken external
          # references show up as a red check (mark this workflow as
          # not-required in branch protection if you don't want it to
          # block merges).
          #
          # On the scheduled cron run we deliberately do NOT fail here,
          # so the issue-filing step below can still execute.
          fail: ${{ github.event_name != 'schedule' }}

      # Scheduled runs file (or update) an issue when something is rotten,
      # so unchanged-but-broken links don't silently sit in main.
      - name: File or update link-rot issue on scheduled failure
        if: github.event_name == 'schedule' && steps.lychee.outputs.exit_code != 0
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          REPO: ${{ github.repository }}
          SERVER_URL: ${{ github.server_url }}
          RUN_ID: ${{ github.run_id }}
        run: |
          set -euo pipefail

          # Create the label on first use; ignore if it already exists.
          gh label create external-reference-rot \
            --color ffaa00 \
            --description "Unreachable external references found by scheduled lychee run" \
            2>/dev/null || true

          run_url="${SERVER_URL}/${REPO}/actions/runs/${RUN_ID}"

          if [ ! -s ./lychee/out.md ]; then
            echo "lychee report file is missing or empty; nothing to file." >&2
            exit 0
          fi

          existing=$(gh issue list \
            --label external-reference-rot \
            --state open \
            --json number \
            --jq '.[0].number // empty')

          if [ -n "$existing" ]; then
            echo "Updating existing issue #$existing"
            gh issue edit "$existing" --body-file ./lychee/out.md
            gh issue comment "$existing" \
              --body "Refreshed by scheduled run: $run_url"
          else
            echo "Filing new issue"
            gh issue create \
              --title "External reference rot detected" \
              --label external-reference-rot \
              --body-file ./lychee/out.md
          fi

SHA-256: 12e227c00d3f5e4b4a2b8f7b9b19c007d931926718f88c4ad4448721f177839b