CLI tool to verify markdown-style local links such as [text](./path/to/file.md).
If a scanned file contains markdown-style links in docs or markdown-style comments, those paths must resolve to real files or link_verifier exits with an error.
- Resolves relative links from the source file's directory, not the working directory
- Handles balanced parentheses in filenames, e.g.
[sheet](Onboarding Sheet (1).xlsx) - Decodes percent-encoded URLs, e.g.
Scope%20Doc.mdresolves toScope Doc.md - Recognises directory links like
[assessment](assessment/) - Skips external links (
http://,https://),mailto:,tel:, bare anchors (#heading), and query-only links (?tab=...) - Strips
#fragmentsuffixes before checking file paths - Exclude files or directories with
--except/-xregex patterns (repeatable)
Download the latest release (native binary; no runtime dependency):
# Example: macOS arm64
gh release download --repo C-Sinclair/link_verifier --pattern 'link_verifier-macos-arm64' --output ~/.local/bin/link_verifier --clobber
chmod +x ~/.local/bin/link_verifierPick the asset matching your platform:
link_verifier-macos-arm64link_verifier-linux-x86_64
Homebrew support is planned after 1.0; for now, GitHub release assets are the fastest path.
make build./link_verifier <target> [target ...]Targets can be:
- A file path (e.g.
README.md) - A directory (recursively scans files under it)
- A wildcard pattern (e.g.
docs/*.md) - Multiple targets in one run
./link_verifier --help
./link_verifier --versionUse -x / --except to skip files matching a regex pattern. The flag can be repeated:
./link_verifier docs/ -x vendor/ -x "test/fixtures"0: all links valid1: usage/target/read error2: broken links found
Run:
./scripts/benchmark.shThe script builds the native binary via dune build and benchmarks the default _build/default/bin/main.exe unless BINARY is set.
Default dataset generated by the script:
- depth:
3 - files per level:
100 - total markdown files:
300 - randomized content with mixed valid/invalid markdown links (
BROKEN_RATE=0.35)
Latest sample run:
single-file-mixed: min=2.59ms avg=3.39ms p95=3.73ms runs=15 exit=2
directory-recursive-mixed: min=8.82ms avg=9.58ms p95=10.32ms runs=15 exit=2
wildcard-pattern-mixed: min=5.25ms avg=5.84ms p95=7.21ms runs=15 exit=2
Environment used for this run (relevant to native binary execution):
OCaml 5.3.0dune 3.21.1macOS 15.7.1(Darwin 24.6.0,arm64)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "./link_verifier docs/*.md"
}
]
}
]
}
}#!/bin/sh
./link_verifier README.md docs/*.md
status=$?
if [ "$status" -ne 0 ]; then
echo "link_verifier failed"
exit "$status"
fiWhen a bad link is found, output includes file + line number, for example:
docs/guide.md:42: broken link -> ./missing-file.md