Skip to content

Check Broken Links

Copy this prompt into your AI assistant (Claude, ChatGPT, Gemini, Cursor, etc.)

When to Use

After renaming, moving, or deleting files in your interview prep repository. Also useful as a periodic health check to ensure all internal markdown links still work.

Prompt

Scan my markdown files for broken internal links.

## Repository Root

{your-repo}/

## Your Tasks

1. **Find all markdown files** in the repository (excluding .git/, .venv/, node_modules/, and template directories).

2. **Extract all internal links** from each file:
   - Relative file links: `[text](path/to/file.md)`
   - Anchor links: `[text](#heading-anchor)`
   - Combined: `[text](path/to/file.md#heading-anchor)`
   - Skip external URLs (http/https)
   - Skip links inside fenced code blocks

3. **Validate each link**:
   - For file references: Does the target file exist at the resolved path?
   - For anchor references: Does the target heading exist in the target file?

4. **Report results**:
   - List all broken links grouped by source file
   - For each broken link, suggest the likely fix:
     - File exists at a different path → suggest the correct path
     - Heading was renamed → suggest the closest matching heading
     - File was deleted → flag for manual review

5. **Summary**: Total files scanned, total links checked, total broken links found.

## Important Rules

- Resolve relative paths from the source file's directory, not the repo root
- Handle both `[text](path)` and `[text](path "title")` formats
- Markdown heading anchors follow GitHub conventions: lowercase, spaces→hyphens, strip special characters
- Don't flag template placeholders like `{company-name}` as broken links

Example Usage

Input: Point the AI at your repository root directory.

Output: A report listing all broken internal links with suggested fixes, plus a summary of total files and links scanned. Run this after any file restructuring to catch stale references.

Alternative: Script-Based Approach

If you prefer an automated script over an AI prompt, here's a Python approach:

"""
Broken link checker for markdown repositories.
Usage: python check_links.py /path/to/repo
"""
import os
import re
import sys
from pathlib import Path

def find_markdown_files(root):
    skip = {'.git', '.venv', 'node_modules', '_templates'}
    for path in Path(root).rglob('*.md'):
        if not any(part in path.parts for part in skip):
            yield path

def extract_links(filepath):
    content = filepath.read_text(encoding='utf-8')
    # Skip fenced code blocks
    content = re.sub(r'```.*?```', '', content, flags=re.DOTALL)
    # Find markdown links (skip external URLs)
    for match in re.finditer(r'\[([^\]]*)\]\(([^)]+)\)', content):
        target = match.group(2).split(' ')[0]  # strip title
        if not target.startswith(('http://', 'https://', 'mailto:')):
            yield target

def heading_to_anchor(heading):
    anchor = heading.lower().strip()
    anchor = re.sub(r'[^\w\s-]', '', anchor)
    anchor = re.sub(r'\s+', '-', anchor)
    return anchor

if __name__ == '__main__':
    root = Path(sys.argv[1] if len(sys.argv) > 1 else '.')
    broken = []
    for md_file in find_markdown_files(root):
        for link in extract_links(md_file):
            file_part, _, anchor = link.partition('#')
            if file_part:
                target = (md_file.parent / file_part).resolve()
                if not target.exists():
                    broken.append((md_file, link, 'file not found'))
                    continue
            # Check anchor if present
            if anchor:
                target_file = (md_file.parent / file_part).resolve() if file_part else md_file
                if target_file.exists():
                    content = target_file.read_text(encoding='utf-8')
                    headings = re.findall(r'^#+\s+(.+)$', content, re.MULTILINE)
                    anchors = [heading_to_anchor(h) for h in headings]
                    if anchor not in anchors:
                        broken.append((md_file, link, 'anchor not found'))

    for source, link, reason in broken:
        print(f"  {source.relative_to(root)}: [{link}] — {reason}")
    print(f"\nTotal broken: {len(broken)}")