Your docs have dead links. I built a zero-dependency CLI that catches the local ones — no network
You rename a file, restructure a folder, tweak a heading — and quietly leave a trail of dead links across your README and docs. Nobody notices until a reader clicks ./old-guide.md and gets a 404.
There are tools for this, but each asks for something:
- markdown-link-check works, but it pulls in ~9 dependencies and its headline feature is hitting external URLs over HTTP — slow, rate-limited, and flaky in CI (your build shouldn't go red because someone's blog was down).
- lychee is fast and great, but it's a Rust binary to install.
- The old Python option,
mlc, has been unmaintained since 2021.
I wanted the deterministic part — local links — to be instant and dependency-free. So I built linkbust :
npx linkbust
README.md:8 ✗ ./setup.md (file not found)
docs/api.md:14 ✗ #usage (no anchor in this file)
guide.md:3 ✗ ./api.md#missing (no anchor in ./api.md)
✗ 3 broken · 142 local links checked · 38 external skipped (never fetched)
What it checks (and skips)
- Relative file links and image sources resolve on disk.
- Anchors — both
[x](#section)in the same file and[x](other.md#section)across files — match a real heading (GitHub-style slug) or an explicit<a id="…">. - Reference links
[text][ref]have a matching[ref]: …definition. - Links inside fenced/inline code blocks are ignored , so examples don't cause false alarms.
It deliberately never makes a network request — http(s), mailto:, and /absolute paths are classified and skipped. That's the whole design: because it's offline, it's instant and can't flake. Which makes it ideal as a pre-commit hook :
- repo: local
hooks:
- id: linkbust
name: linkbust
entry: npx linkbust
language: system
pass_filenames: false
Exit code is 1 if anything's broken, so CI fails cleanly.
Zero dependencies, both ecosystems
Pure standard library — a small Markdown parser (code masking, reference links, GitHub anchor slugs) plus filesystem checks. The Node and Python ports are behavior-identical, byte-for-byte (same output, same exit codes).
npx linkbust # Node >= 18
pip install linkbust # Python >= 3.8
- npm: https://www.npmjs.com/package/linkbust
- PyPI: https://pypi.org/project/linkbust
- GitHub: https://github.com/jjdoor/linkbust
Do you check links in CI, or only find out when someone reports a 404? And for external URLs — worth the flakiness to catch them, or do you treat those as out of scope too?
Discussion in the ATmosphere