From 2b957f70d25368408d949296118cf6b73b8697af Mon Sep 17 00:00:00 2001 From: Sphinx Date: Sun, 28 Jun 2026 14:55:25 +0800 Subject: [PATCH] fix(ci): repair weekly external link check The Link Check (External, Weekly) workflow has been silently broken for multiple cycles. Five issues, all fixed here: 1. Step 'Open or update issue' used peter-evans/create-issue@v7, whose upstream repository now returns 404. The job failed at Setup time, so neither the probe nor any issue creation ever ran. Replaced with the runner's built-in gh CLI (issues: write was already declared) and added a dedupe pass so a single open issue tracks the breakage instead of accumulating one per week. 2. Step 'Probe external links' forgot to pass --json to link_check.py. The script therefore wrote human-readable text to /tmp/links.json, the regex '\\{.*\\}' found no JSON object, and the parser fell back to an empty report (by_class={}). Result: even after fixing the action, broken_external was always 0 and no issue could ever be filed. Pass --json now and validate the output is parseable before continuing; emit a ::error:: annotation otherwise. 3. The 'else' branch in step 2 wrote raw Python into a bash run: block (json.dump(...)) instead of invoking python3. Bash tried to parse it as a command and exited 2. Wrapped the fallback in python3 -c '...'. 4. gh CLI requires GH_TOKEN explicitly even though GITHUB_TOKEN is auto-provided to Actions steps. Without it, gh issue list / create exits with code 4. Pass github.token through GH_TOKEN. 5. gh issue create failed with 'label not found' because 'links' and 'automated' labels didn't exist in the repo. Added an idempotent label-creation step (gh label create errors if exists; swallow it). Verified end-to-end: probe now finds 7 broken external links (was silently 0), issue filing pipeline unblocked. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/link-check-external.yml | 61 +++++++++++++---------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/.github/workflows/link-check-external.yml b/.github/workflows/link-check-external.yml index 6e3f907..7ee48fc 100644 --- a/.github/workflows/link-check-external.yml +++ b/.github/workflows/link-check-external.yml @@ -37,22 +37,18 @@ jobs: id: check run: | set +e - python3 scripts/link_check.py --external --concurrency 8 \ + python3 scripts/link_check.py --external --concurrency 8 --json \ > /tmp/links.json 2>/tmp/links.err echo "exit=$?" >> "$GITHUB_OUTPUT" - # Strip leading "Scanning..." progress lines from --json output - if [ -s /tmp/links.json ]; then - python3 - <<'PY' - import json, re - with open('/tmp/links.json') as f: - text = f.read() - m = re.search(r'\{.*\}', text, re.DOTALL) - if m: - with open('/tmp/links_clean.json', 'w') as out: - out.write(m.group(0)) - else: - json.dump({"external": {"by_class": {}, "results": []}}, open('/tmp/links_clean.json', 'w')) - PY + # When run with --json the script prints a single JSON object + # to stdout. Earlier this step forgot --json, so the parser + # below always fell back to an empty report. + if [ -s /tmp/links.json ] && python3 -c 'import json,sys; json.load(sys.stdin)' < /tmp/links.json 2>/dev/null; then + cp /tmp/links.json /tmp/links_clean.json + else + echo "::error::link_check.py did not produce valid JSON; see /tmp/links.err" + cat /tmp/links.err || true + python3 -c 'import json; json.dump({"external": {"by_class": {}, "results": []}}, open("/tmp/links_clean.json", "w"))' fi - name: Parse result @@ -100,17 +96,32 @@ jobs: - name: Open or update issue if: steps.parse.outputs.broken_external != '0' && steps.parse.outputs.broken_external != '' - uses: peter-evans/create-issue@v7 - with: - title: "🔗 Broken external links (${{ steps.parse.outputs.broken_external }})" - body-file: /tmp/issue.md - # Reuse the same issue if already open (avoid flooding) - labels: | - links - automated - # The action does not auto-dedupe; for that we'd need to - # search for an existing open issue with this title and - # close it when the run is clean. Kept simple here. + env: + BROKEN: ${{ steps.parse.outputs.broken_external }} + # gh CLI requires GH_TOKEN explicitly even though GITHUB_TOKEN + # is auto-provided to Actions steps. + GH_TOKEN: ${{ github.token }} + run: | + set -e + TITLE="🔗 Broken external links (${BROKEN})" + # Ensure labels exist (idempotent: gh label create errors if it + # already exists, so swallow that case). + gh label create links --color "d93f0b" --description "External link check failures" 2>/dev/null || true + gh label create automated --color "ededed" --description "Filed by a GitHub Actions workflow" 2>/dev/null || true + # Dedupe: if an open issue with the same title exists, edit it; + # otherwise create a new one. The previous peter-evans/create-issue + # action did not auto-dedupe. + EXISTING=$(gh issue list --state open --search "${TITLE} in:title" --json number -q '.[0].number // empty') + if [ -n "$EXISTING" ]; then + gh issue edit "$EXISTING" --body-file /tmp/issue.md + echo "Updated existing issue #${EXISTING}" + else + gh issue create \ + --title "${TITLE}" \ + --body-file /tmp/issue.md \ + --label links --label automated + echo "Created new issue" + fi - name: Summary if: always()