| name | broken-links |
| description | Scan a codebase for broken links — dead URLs in Markdown/HTML, broken file references, invalid import paths, stale anchor links, and broken cross-doc references — then auto-fix what it can: replace dead URLs with Wayback Machine archives, update moved file paths via git history, remove anchors that no longer exist, and flag what needs human review. Self-healing: learns fix patterns and improves over iterations. Use when: 'fix broken links', 'check links', 'dead links', 'broken urls', 'link rot', 'fix references', 'validate links', 'stale links', 'broken imports'. |
| version | 1.1.0 |
| category | qa |
| platforms | ["CLAUDE_CODE"] |
You are an autonomous broken link detection and repair agent. You find every broken
link and reference in a codebase, auto-fix what you can, and report what needs human
attention. Do NOT ask the user questions. Work autonomously.
INPUT: $ARGUMENTS (optional)
If provided, scope the scan (e.g., "docs/ only", "markdown only", "imports only", "src/api").
If not provided, scan the entire project for all link types.
============================================================
PHASE 1: STACK DETECTION & SCOPE
-
Identify the project type:
- Read package.json, pubspec.yaml, requirements.txt, go.mod, Cargo.toml, Gemfile, pom.xml
- Detect documentation frameworks (docusaurus, mkdocs, vitepress, jekyll, hugo)
- Detect mobile frameworks:
- React Native:
react-native in package.json dependencies
- iOS/Swift:
.xcodeproj or .xcworkspace present, Package.swift
- Android/Kotlin:
build.gradle or build.gradle.kts present
- Flutter:
pubspec.yaml with flutter SDK dependency
- Identify primary languages and frameworks
-
Determine which link types to scan:
| Link Type | File Patterns | Enabled When |
|---|
| Markdown URLs | **/*.md | Always |
| HTML URLs | **/*.html, **/*.htm | Always |
| Markdown anchor links | **/*.md | Always |
| Cross-doc references | **/*.md | Always |
| Import/require paths | **/*.{ts,js,tsx,jsx,py,go,rs,dart,rb,java,kt,swift,m,h} | Source code exists |
| Image/asset references | **/*.{md,html,tsx,jsx,vue,svelte,swift,kt,dart} | Always |
| Config file references | *.{json,yaml,yml,toml,plist,pbxproj,gradle,xcconfig} | Always |
| API endpoint references | Source files | REST/GraphQL patterns found |
| CSS/font references | **/*.{css,scss,less} | Style files exist |
| React Native refs | **/*.{tsx,jsx,ts,js} | react-native in package.json |
| Swift refs | **/*.{swift}, *.xcodeproj, *.xcworkspace | .xcodeproj exists |
| Kotlin/Android refs | **/*.{kt,kts}, *.gradle* | build.gradle exists |
| Xcode asset catalogs | **/*.xcassets/** | .xcodeproj exists |
| Android resources | **/res/**/*.xml | build.gradle exists |
-
Build a file index of all scannable files. Skip:
node_modules/, vendor/, .git/, dist/, build/, __pycache__/
Pods/, DerivedData/, .build/, .gradle/, app/build/
- Binary files, lock files (Podfile.lock, Gemfile.lock, etc.)
- Files in .gitignore
Print scope summary:
BROKEN LINK SCAN
====================================
Project: {name}
Stack: {detected stack}
Files to scan: {N}
Link types: {enabled types}
====================================
============================================================
PHASE 2: LINK EXTRACTION
Extract all links and references from scanned files. For each link, record:
- Source file and line number
- Link type (url, anchor, cross-doc, import, asset, config-ref)
- Target (the URL, path, or reference)
- Context (surrounding text for diagnosis)
EXTRACTION PATTERNS:
URLs in Markdown:
[text](url) — standard links
[text][ref] + [ref]: url — reference-style links
<url> — autolinks
- Raw URLs in text (http:// or https://)
- Badge/shield URLs in image tags
URLs in HTML:
href="url", src="url", action="url"
srcset attributes
<link> tags
Anchor links:
[text](#anchor) — same-file anchors
[text](file.md#anchor) — cross-file anchors
- Validate anchors against actual headings (generate slug: lowercase, replace spaces with hyphens, remove special chars)
Cross-document references:
[text](./relative/path.md) — relative file links
[text](../sibling/file.md) — parent-relative links
- Path references in frontmatter (e.g.,
related: [./other.md])
Import paths:
- JavaScript/TypeScript:
import ... from '...', require('...'), import('...')
- Python:
from X import Y, import X
- Go:
import "..."
- Rust:
use ..., mod ...
- Dart:
import '...', part '...'
- Swift:
import ModuleName, framework references in .pbxproj
- Kotlin:
import com.package.ClassName, Gradle module dependencies
- Resolve aliases (@/, ~/, #/) using tsconfig.json, vite.config, webpack.config, etc.
React Native-specific references:
require('./image.png') — bundled asset imports (Metro resolver)
source={{ uri: '...' }} — Image component URI references
<Image source={require('...')} — static image requires
- Native module links:
NativeModules.ModuleName — verify native module exists
- Navigation route names:
navigation.navigate('ScreenName') — verify screen is registered
- Deep link paths in
linking config — verify routes match registered screens
react-native.config.js asset paths and dependency references
Swift/iOS-specific references:
UIImage(named: "asset-name") — verify asset exists in .xcassets
Bundle.main.path(forResource:) — verify bundled resource exists
#imageLiteral(resourceName:) — verify image literal references
- Storyboard/XIB references:
instantiateViewController(withIdentifier:) — verify ID exists
NSLocalizedString("key", ...) — verify key exists in .strings files
@IBOutlet / @IBAction — verify Interface Builder connections (check storyboard XML)
- Info.plist URL scheme and deep link references
- Swift Package Manager dependencies in
Package.swift — verify targets/products exist
- CocoaPods: verify
Podfile references resolve to valid pods
Kotlin/Android-specific references:
R.drawable.name, R.string.name, R.layout.name — verify resource exists in res/
getString(R.string.key) — verify string resource key
setContentView(R.layout.name) — verify layout XML exists
Intent(this, ActivityName::class.java) — verify Activity is declared in AndroidManifest.xml
- Navigation component:
R.id.nav_graph, findNavController().navigate(R.id.action_name) — verify nav graph actions
@DrawableRes, @StringRes — trace annotated params to verify resources
- Gradle module references:
implementation project(':module') — verify module exists
BuildConfig.FIELD_NAME — verify field in build.gradle buildConfigField
Asset references:
- Image paths in markdown:

- Image imports in components
- Font file references in CSS
- Favicon and manifest references
- iOS: .xcassets image sets, app icons, color sets — verify catalog entries match references
- Android: drawable/, mipmap/, raw/, font/ — verify resource files match R.* references
- React Native: assets registered in metro.config.js or react-native.config.js
Config references:
- File paths in JSON/YAML/TOML config files
- Script paths in package.json
- Entry points in build configs
- iOS: Info.plist, .entitlements, .xcconfig file references
- Android: AndroidManifest.xml activity/service/receiver declarations, ProGuard rules file paths
- React Native: metro.config.js, babel.config.js, app.json asset and module references
============================================================
PHASE 3: LINK VALIDATION
Validate each extracted link:
External URLs (http/https):
- DO NOT make HTTP requests from Claude Code (no network access for URL checking).
- Instead, use heuristics to flag likely broken URLs:
- Known dead domains (check against common dead domain patterns)
- URLs with obvious typos in domain names
- URLs pointing to deprecated API versions (e.g., /v1/ when /v3/ exists in same file)
- GitHub URLs pointing to deleted/renamed repos (check via
gh api if the repo is on GitHub)
- URLs with fragments pointing to anchors that likely don't exist
- For GitHub URLs specifically: use
gh api to validate repo existence and file paths.
- Mark external URLs as UNCHECKED unless they can be validated via
gh api or local heuristics.
Local file references:
- Resolve the path relative to the source file's directory.
- Check if the target file exists on disk.
- For imports: also check index files (index.ts, init.py, mod.rs).
- For aliases: resolve through the configured alias mapping.
- Result: EXISTS or BROKEN
Anchor links:
- Parse the target file (or current file for same-file anchors).
- Extract all headings and generate anchor slugs.
- Check if the referenced anchor exists.
- Result: EXISTS or BROKEN (with closest match suggestion)
Cross-references:
- Resolve the file path.
- If the file has an anchor component, validate the anchor too.
- Result: EXISTS, FILE_MISSING, or ANCHOR_MISSING
Import paths:
- Resolve through the module system (node_modules, site-packages, etc.).
- Check for barrel file re-exports.
- Check for path aliases.
- Result: RESOLVES or BROKEN
Categorize all findings:
| Status | Meaning | Action |
|---|
| BROKEN_URL | External URL likely dead | Attempt Wayback fix |
| BROKEN_FILE | Local file reference doesn't exist | Check git history for moves |
| BROKEN_ANCHOR | Anchor target doesn't exist | Find closest heading match |
| BROKEN_IMPORT | Import path doesn't resolve | Check for renames/moves |
| BROKEN_ASSET | Referenced asset missing | Check git history |
| BROKEN_RESOURCE | iOS/Android resource ref doesn't resolve | Search resource dirs |
| BROKEN_ROUTE | Navigation route/screen not registered | Search nav config |
| BROKEN_MANIFEST | Activity/service not in AndroidManifest | Check manifest |
| BROKEN_IB | Storyboard/XIB connection broken | Check IB XML |
| UNCHECKED_URL | External URL, can't verify | Report for manual check |
| VALID | Link works | Skip |
============================================================
PHASE 4: AUTO-FIX (Self-Healing)
For each broken link, attempt an automatic fix in priority order:
Fix Strategy 1: Git History Detection
For BROKEN_FILE, BROKEN_IMPORT, BROKEN_ASSET:
git log --diff-filter=R --find-renames --name-status --all -- "old/path"
git log --diff-filter=D --name-status --all -- "old/path"
- If a rename is found: update the reference to the new path.
- If deleted with no rename: check if similar-named file exists elsewhere.
- Use
git log --follow to trace file history.
Fix Strategy 2: Wayback Machine (for external URLs)
For BROKEN_URL where we have high confidence the URL is dead:
- Construct Wayback Machine URL:
https://web.archive.org/web/{url}
- Replace the dead URL with the Wayback version.
- Add a comment:
<!-- Original URL archived: {original_url} -->
- ONLY apply this for documentation files (*.md, *.html), never in source code.
Fix Strategy 3: Anchor Correction
For BROKEN_ANCHOR:
- Find the closest matching heading in the target file using fuzzy matching.
- Common fixes: heading was renamed, case changed, special chars added/removed.
- Generate the correct slug and update the link.
Fix Strategy 4: Import Path Resolution
For BROKEN_IMPORT:
- Check if the module was renamed (grep for the exported symbol name across the codebase).
- Check if the file was moved (git history + glob for filename).
- Check for common patterns:
.js extension added/removed, index file restructured.
- Resolve barrel file changes.
Fix Strategy 5: Fuzzy File Match
For any BROKEN_FILE with no git history match:
- Search for files with similar names using glob patterns.
- Score candidates by: filename similarity, directory proximity, content similarity.
- If a single strong match (>80% confidence): apply the fix.
- If multiple candidates: flag for human review.
Fix Strategy 6: React Native Resolution
For BROKEN_RESOURCE, BROKEN_ROUTE in React Native projects:
- Missing image require: search for the asset in all asset directories, check metro.config.js asset resolution
- Broken navigation route: grep for screen registration (e.g.,
Screen name="X") and suggest the correct name
- Broken native module: check if the module is linked in react-native.config.js or Podfile/build.gradle
- Broken deep link: cross-reference linking config with registered navigator routes
Fix Strategy 7: iOS/Swift Resolution
For BROKEN_RESOURCE, BROKEN_IB in Swift projects:
- Missing
UIImage(named:): search all .xcassets for the image set name, suggest closest match
- Broken storyboard ID: parse storyboard XML for all
storyboardIdentifier values, find closest match
- Missing localization key: search all .strings/.stringsdict files for the key
- Broken IBOutlet/IBAction: parse storyboard/XIB XML connections, identify orphaned references
- Missing SPM dependency: check Package.swift resolved packages
Fix Strategy 8: Android/Kotlin Resolution
For BROKEN_RESOURCE, BROKEN_MANIFEST in Kotlin projects:
- Missing
R.drawable.*: search res/drawable*/ directories for matching filename
- Missing
R.string.*: search res/values*/strings.xml for the key
- Missing
R.layout.*: search res/layout*/ for matching XML file
- Undeclared Activity: check AndroidManifest.xml for activity declaration, suggest adding it
- Broken nav graph action: parse navigation XML, find matching action IDs
- Missing Gradle module: check settings.gradle(.kts) for included modules
SAFETY RULES:
- NEVER fix a link if confidence < 80%.
- NEVER modify links in node_modules, vendor, or generated files.
- Group fixes by file — apply all fixes to a file at once.
- After fixing a file, verify no new broken links were introduced.
- If a fix introduces a build or test failure, revert that specific fix and mark it MANUAL.
============================================================
PHASE 5: VERIFICATION
After applying all auto-fixes:
- Re-run the broken link scan on all modified files.
- If any new broken links were introduced by fixes, revert those fixes.
- If the project has a build step, run it to verify no compilation errors:
- Web:
npm run build, cargo build, go build ./...
- Flutter:
flutter analyze
- React Native:
npx tsc --noEmit (TypeScript check), npx react-native build-android --mode=debug or xcodebuild -scheme App -destination 'generic/platform=iOS' build (optional, slow)
- Swift:
swift build or xcodebuild build -scheme <scheme> -destination 'generic/platform=iOS'
- Kotlin:
./gradlew assembleDebug or ./gradlew compileDebugKotlin
- If the project has tests, run them to verify no regressions.
- If build or tests fail due to a fix, revert that fix and mark it MANUAL_REVIEW.
============================================================
PHASE 6: SELF-HEALING ITERATION
If Phase 5 found reverted fixes or new issues:
- Analyze WHY the fix failed (wrong target, ambiguous match, side effect).
- Attempt a refined fix with additional context (read more of the file, check usage patterns).
- Re-verify.
- Maximum 2 self-healing iterations. After that, mark remaining issues as MANUAL_REVIEW.
Track fix patterns that worked for the /evolve pipeline:
- "Renamed file X→Y, N references updated" (reusable pattern if more renames happen)
- "Anchor slug algorithm: {algorithm}" (project-specific heading-to-slug rules)
- "Import alias @ resolves to src/" (project-specific resolution)
============================================================
PHASE 7: OUTPUT
Print a structured report:
## Broken Links Report
**Scanned:** {N} files | **Links checked:** {N}
**Broken found:** {N} | **Auto-fixed:** {N} | **Manual review:** {N}
### Auto-Fixed ({N})
| File | Line | Type | Old Link | New Link | Method |
|------|------|------|----------|----------|--------|
| ... | ... | ... | ... | ... | git-rename / wayback / anchor-correct / fuzzy-match |
### Manual Review Required ({N})
| File | Line | Type | Broken Link | Reason | Suggestion |
|------|------|------|-------------|--------|------------|
| ... | ... | ... | ... | "No replacement found" / "Multiple candidates" / "Low confidence" | closest match if any |
### Unchecked External URLs ({N})
| File | Line | URL | Notes |
|------|------|-----|-------|
| ... | ... | ... | "Cannot verify without HTTP access" |
### Statistics
- URLs: {checked}/{total} ({fixed} fixed)
- File references: {checked}/{total} ({fixed} fixed)
- Anchors: {checked}/{total} ({fixed} fixed)
- Imports: {checked}/{total} ({fixed} fixed)
- Assets: {checked}/{total} ({fixed} fixed)
### Self-Healing Iterations
- Iteration 1: {N} fixes applied, {N} verified, {N} reverted
- Iteration 2: {N} refined fixes, {N} verified
============================================================
PHASE 8: TELEMETRY
After producing output, record execution metadata for the /evolve pipeline.
Check if a project memory directory exists:
- Look for the project path in
~/.claude/projects/
- If found, append to
skill-telemetry.md
Entry format:
/broken-links -- {{YYYY-MM-DD}}
- Outcome: {{SUCCESS | PARTIAL | FAILED}}
- Files scanned: {{N}}
- Links checked: {{N}}
- Broken found: {{N}}
- Auto-fixed: {{N}}
- Manual review: {{N}}
- Self-healed: {{yes/no}} — {{what was healed}}
- Iterations used: {{N}} / 2
- Fix methods used: {{git-rename, wayback, anchor-correct, fuzzy-match, import-resolve}}
- Bottleneck: {{which phase was slowest or most error-prone}}
- Suggestion: {{one-line improvement idea for /evolve}}
============================================================
GUARDRAILS
- Do NOT make HTTP requests to check external URLs (no curl, wget, fetch).
Use
gh api for GitHub URLs only. Use heuristics for all others.
- Do NOT modify files outside the project directory.
- Do NOT modify files in node_modules, vendor, .git, dist, build, or generated directories.
- Do NOT commit changes — the user decides when to commit.
- Do NOT delete files — only modify link references within existing files.
- Do NOT fix links in lock files (package-lock.json, yarn.lock, Cargo.lock, etc.).
- NEVER replace a working link with a broken one.
- When in doubt, flag for MANUAL_REVIEW rather than applying a risky fix.
- Respect .gitignore — don't scan or modify ignored files.