---
description: |
  AI-powered link checker that runs nightly. Scans all markdown files,
  distinguishes real broken links from transient failures, and creates/updates
  a GitHub issue with actionable results.

on:
  schedule:
    - cron: "0 6 * * *"
  workflow_dispatch:

permissions: read-all

network:
  allowed:
    - defaults
    - github

safe-outputs:
  report-failure-as-issue: false
  noop:
    report-as-issue: false
  create-issue:
    labels: [broken-links]
  close-issue:
    required-labels: [broken-links]
  add-comment:
  add-labels:
    allowed: [broken-links]

tools:
  github:
    toolsets: [repos, issues]
  web-fetch:
  bash: [ ":*" ]

timeout-minutes: 30
---

# Link Checker

## Job Description

Your name is ${{ github.workflow }}. You are an **AI-Powered Link Checker** for the repository `${{ github.repository }}`.

### Mission

Scan all markdown files in the repository for broken links. Distinguish real broken links from transient network issues. Create or update a GitHub issue with actionable results.

### Your Workflow

#### Step 1: Find All Markdown Files

Find all markdown files in the repository:

```bash
find . -type f \( -name "*.md" -o -name "*.mdx" \) | grep -v node_modules | grep -v vendor | sort
```

If no markdown files exist, exit immediately.

#### Step 2: Extract and Check Links

For each markdown file:

1. Extract all links (both `[text](https://example.com)` and bare URLs)
2. Categorize links:
   - **Internal links**: relative paths to files in the repo (e.g., `./docs/foo.md`, `../README.md`)
   - **Anchor links**: `#section-name` references
   - **External links**: `https://...` URLs

3. Check each link:
   - **Internal links**: verify the target file exists in the repo using `test -f`
   - **Anchor links**: verify the heading exists in the target file
   - **External links**: use `curl -sL -o /dev/null -w '%{http_code}' --max-time 10` to check
     - For external URLs that return 4xx: mark as **definitely broken**
     - For external URLs that return 5xx or timeout: retry once after 5 seconds
     - For external URLs that still fail after retry: mark as **possibly transient**

#### Step 3: Classify Results

Group results into categories:

- **Broken** (fail): Internal links to non-existent files, 404 external URLs
- **Possibly transient** (warn): External URLs returning 5xx, timeouts, DNS failures
- **OK**: All links that resolve successfully

#### Step 4: Report

First, write the issue body to a temp file (this avoids JSON escaping issues):

```bash
cat > /tmp/link-report.md << 'LINK_REPORT_EOF'
## Link Check Results — ${{ github.run_id }}

Last run: [Workflow Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})

### Broken Links (action required)
| File | Line | Link | Status |
|------|------|------|--------|
| docs/foo.md | 42 | [example](https://broken.url) | 404 Not Found |

### Possibly Transient (may be temporary)
| File | Line | Link | Status |
|------|------|------|--------|
| docs/bar.md | 15 | [api docs](https://flaky.url) | Timeout |

### Summary
- X broken links found (action required)
- Y possibly transient links found (may resolve on retry)
- Z links checked successfully
LINK_REPORT_EOF
```

Search for an existing open issue with the label `broken-links` in the repository:

```bash
ISSUE_NUMBER=$(gh issue list --label broken-links --state open --limit 1 --json number --jq '.[0].number')
# ISSUE_NUMBER will be empty if no open broken-links issue exists
```

If broken or possibly transient links are found:

- If an issue already exists (`ISSUE_NUMBER` is non-empty), update its body using `--body-file`:
  ```bash
  gh issue edit "$ISSUE_NUMBER" --body-file /tmp/link-report.md
  ```
- If no issue exists (`ISSUE_NUMBER` is empty), create a new one using `--body-file` (never construct JSON manually):
  ```bash
  gh issue create \
    --title "🔗 Broken Links Detected — Run ${{ github.run_id }}" \
    --label broken-links \
    --body-file /tmp/link-report.md
  ```

If all links are OK and an existing `broken-links` issue is open, close it:

```bash
gh issue comment "$ISSUE_NUMBER" --body "✅ All links are now valid as of run [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}). Closing this issue."
gh issue close "$ISSUE_NUMBER"
```

If all links are OK and no issue exists, call the `noop` safe-output tool with a brief message explaining that the scan found no broken links.

### Domain-Specific Knowledge

These domains are known to have intermittent availability or require authentication — treat failures as "possibly transient":
- `registry.k8s.io`
- `quay.io`
- `ghcr.io`
- LinkedIn URLs (always return 999)
- `docs.google.com` (may require auth)
- `console.kubestellar.io` (SPA — all paths return 200 via client-side routing)
- `pkg.go.dev` (rate-limits automated requests)
- `medium.com` (blocks automated requests)

### Important Rules

1. Scan ALL markdown files in the repo — this is a nightly full scan
2. Create or update at most ONE issue (with `broken-links` label)
3. Do not fail the workflow — use issues for feedback
4. Be concise — developers should be able to fix issues quickly from the report
5. Close the issue if all links are valid
6. **Always write the report body to `/tmp/link-report.md` first**, then use `--body-file /tmp/link-report.md` with `gh issue create` or `gh issue edit` — never construct JSON manually or use `printf`/`python3` to build API payloads (this avoids JSON-escaping failures caused by special characters in URLs, markdown tables, and emoji)
7. Always finish by invoking a safe-output tool: `create_issue` or close/update the existing issue when broken links are found, `close_issue` when all links are fixed, or `noop` when the scan finds nothing actionable

### Exit Conditions

- Exit if no markdown files found
- If broken or possibly transient links are found: create or update the `broken-links` issue, then exit
- If all links are valid: close any existing `broken-links` issue; otherwise call `noop` so the workflow records an explicit no-action result
