plugins/aem/edge-delivery-services-content-ops/skills/link-rot-scanner/SKILL.md
Crawl and validate all internal and external links across an AEM Edge Delivery Services site. Uses the query index or sitemap to discover pages, extracts links from .plain.html renditions, checks HTTP status codes, and produces a prioritized report of broken, redirecting, and insecure links. Use when auditing link health before launch, after a migration, or as a periodic maintenance check.
npx skillsauth add adobe/skills link-rot-scannerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Discover all pages on an AEM Edge Delivery Services site using the query index or sitemap, extract every link from each page's .plain.html rendition, validate each link's HTTP status, and produce a prioritized report of broken, redirecting, and insecure links with suggested fixes.
When fetching or analyzing external URLs:
Before starting, create a TodoList to track progress through each step:
.plain.html and extract all linksAsk the user for the site's base URL (e.g., https://www.example.com).
Attempt to discover all pages in this order:
Query index (preferred)
Fetch {base-url}/query-index.json. Each entry includes path, title, description, lastModified, and image. Extract the path field from each entry. If the response is paginated (look for offset and limit or total), fetch all pages by following the pagination.
Sitemap fallback
If the query index is not available (404 or empty), fetch {base-url}/sitemap.xml and parse the <loc> elements.
Manual page list If neither source is available, ask the user for a list of page URLs, one per line.
For large sites (over 100 pages), inform the user and process pages in batches of 10-20.
For each page, fetch its .plain.html rendition (e.g., /about becomes /about.plain.html; root / becomes /index.plain.html).
Extract all <a href="..."> elements and record:
Classify each link as: Internal (same domain), External (different domain), Anchor (fragment-only like #section), or Non-HTTP (mailto:, tel:, javascript:).
Deduplicate URLs first — if the same URL appears on 50 pages, check it once.
For each unique internal URL, make an HTTP GET request. Check both the path and the path with a trailing slash (EDS may serve content at either). For fragment links, verify the target id exists in the .plain.html.
For each unique external URL, send an HTTP HEAD request. Fall back to GET if HEAD returns 405. Example using WebFetch:
# Check a single external link — HEAD first, GET fallback
response = fetch(url, method="HEAD", timeout=15000,
headers={"User-Agent": "EDS-LinkCheck/1.0"})
if response.status == 405:
response = fetch(url, method="GET", timeout=15000,
headers={"User-Agent": "EDS-LinkCheck/1.0"})
Wait 500ms between requests to the same external domain. Flag 403/5xx/timeout responses as "unable to verify" rather than "broken" since bot detection may cause false negatives.
Group all non-200 links by priority (see references/link-validation-details.md for full definitions):
| Priority | Category | |----------|----------| | P0 | Broken internal links (404) — always highest priority | | P1 | Broken external links (404) | | P2 | Redirecting links (301/302) — update to final destination | | P3 | Insecure links (HTTP instead of HTTPS) | | P4 | Unable to verify (403/5xx/timeout) | | Info | Anchor issues (missing fragment target) |
| Priority | Category | Count | |----------|----------|-------| | P0 | Broken internal links | X | | P1 | Broken external links | Y | | P2 | Redirecting links | Z | | P3 | Insecure links (HTTP) | A | | P4 | Unable to verify | B | | Info | Anchor issues | C | | -- | Valid links (200) | D | | Total | | N |
For each page with at least one non-200 link:
Page: /path/to/page
| Priority | Link URL | Anchor Text | Status | Suggested Fix |
|----------|----------|-------------|--------|---------------|
| P0 | /old-page | "Learn more" | 404 | Update to /new-page or remove link |
| P2 | /about | "About us" | 301 -> /about-us | Update link to /about-us |
| P3 | http://example.com | "Example" | 200 (HTTP) | Change to https://example.com |
For broken internal links:
For fix instructions authors can follow in their authoring tool, see references/link-validation-details.md.
tools
Use the run-workflow MCP to discover, compose, execute, publish, and save Adobe Firefly workflows. TRIGGER when: user asks what actions are available, what the MCP can do, how to process images/video/3D via workflow, wants to build/run/save/publish a workflow, OR pastes any workflow/batch/execution ID. BARE ID (UUID/workflowId/batchId) = INSPECT ONLY — call inspect_run, NEVER run_workflow_submit. ALWAYS call list_actions first for capability/discovery questions. DO NOT TRIGGER for direct Firefly API calls without MCP (use firefly-api-specs).
tools
Run predefined featured workflows via run-workflow MCP. TRIGGER when user names a featured workflow (retargeting, banners at scale, localization, packaging, banner advertising, etc.) or asks to run a known marketing/production workflow. Requires run-workflow MCP. ALWAYS call get_featured_workflow before compose_workflow. DO NOT TRIGGER for custom one-off workflows with no named template — use run-workflow skill.
tools
Migrate an Adobe Commerce App Builder project from the Integration Starter Kit or Checkout Starter Kit to the new App Management approach. Run from the root of the App Builder project to be migrated. Pass --auto to skip confirmation prompts (suitable for CI or batch use) — auto mode prints a summary of all Q&A questions answered with their defaults. Pass --doc-scan-only to scan README.md and env.dist for outdated content without modifying any files. Use when the user wants to migrate an App Builder project from the Integration Starter Kit or Checkout Starter Kit to the App Management approach, or mentions upgrading their Adobe Commerce extension architecture.
development
Add or modify webhook interceptors in an Adobe Commerce app. Use when the user wants to intercept Commerce operations to validate input, append data, or modify behavior — before or after execution. Requires a base app initialized with commerce-app-init.