All posts
Jun 4, 2026 · 5 min read · Nadun

Why your README always lies (and how to stop it)

Docs don't rot because developers are lazy. They rot because nothing connects them to the code. Here's the structural fix — and why 'just be disciplined' never works.

Open any repo you haven't touched in three months and read the README out loud. Somewhere in the setup section there's a command that doesn't exist anymore, an env var you renamed, or a "Getting Started" that starts somewhere the code no longer is.

It's not that you wrote bad docs. You wrote accurate docs — once. Then you shipped twelve PRs, and each one made the README a little more wrong. Nobody noticed, because nobody reads their own README until a new teammate does, gets stuck, and pings you on Slack.

This is doc rot, and it's nearly universal. The interesting question isn't whether your docs drift — it's why every team's docs drift the same way, no matter how disciplined they are.

The real reason docs rot

Docs rot because there is no mechanical link between your code and your documentation.

Think about what does stay current in a healthy repo. Tests stay current, because CI fails when they don't. Types stay current, because the compiler yells. Dependencies stay current-ish, because a bot opens PRs. Every one of those has a forcing function — something automatic that makes the gap visible and annoying.

Documentation has none of that. The diff merges, the README doesn't change, and absolutely nothing happens. No red X, no failing check, no bot. The cost of stale docs is real but it's deferred and diffuse — a confused contributor next month, a support ticket next quarter — so it always loses to the thing that's due today.

That's the trap: docs are the one artifact in your repo with no feedback loop.

Why "just be disciplined" never works

The usual fixes all attack the symptom, not the structure:

"Add docs to the PR checklist." Checklists are a tax on the author, paid at the exact moment they have the least energy — right before merge, when they just want the thing to ship. So the box gets ticked, or the line gets deleted, and the docs still don't change.

"Move to docs-as-code." Putting your docs in the repo as Markdown is genuinely good — it puts them next to the code and into review. But it doesn't write the update. A docs/ folder full of Markdown rots exactly as fast as a README if nobody edits it on each change.

"Use a docs platform." Tools like Mintlify and ReadMe make docs lovely to host and read. They're great at that. But they don't watch your merges either — a human still has to notice the code changed and go write the update. (More on where they fit in a second.)

Every one of these still depends on a human remembering, under deadline, to do unrewarded work. That's not a discipline problem you can solve with more discipline. It's a missing forcing function.

The fix: make docs a side effect of merging

If the problem is that nothing connects the merge to the docs, the fix is to connect them. Treat the documentation update the same way you treat a test run: something that happens automatically when code changes, and shows up where you already review things — a pull request.

Concretely, the loop looks like this:

  1. You merge a code PR.
  2. Something reads the diff and drafts the matching doc/CHANGELOG update.
  3. It opens that update as its own PR.
  4. You review it like any other contributor's PR — approve, tweak, or close.

Now docs have a forcing function. The gap is visible (there's a PR sitting there), the work is pre-done (you're reviewing, not writing from scratch), and you never lose control (nothing merges without you).

The part everyone's (rightly) afraid of

The obvious objection to "let something draft my docs" is: won't it hallucinate? Won't it confidently document a function that doesn't exist, or invent a flag I never added?

This is the right fear, and it's exactly why the naive version — paste the diff into a chat model, paste the output into your docs — is dangerous. A general model will happily write plausible nonsense.

The fix is to constrain the generation, not trust it. The rule that matters most: anything the draft says has to be grounded in the actual diff. If the generated text mentions an identifier — a function, a file, a symbol — that isn't present in the change it's documenting, that draft gets rejected, not published. "Grounded in the diff" turns the model from a creative writer into a careful summarizer of something real.

That single constraint kills the scariest failure mode. It can't document an API that doesn't exist, because the API has to be in the diff for it to write about it.

Where your docs platform still fits

None of this replaces a docs host. If you're on Mintlify, ReadMe, or a Docusaurus site, keep it — that's where your docs live and look good. The missing piece was never hosting; it was who writes the update when the code changes. Auto-drafted PRs feed your existing site; they don't compete with it. (We wrote up the hosting-vs-updating distinction here if you want the longer version.)

Try it on one repo

This is the idea behind docs-keeper: a GitHub App that opens a documentation PR every time you merge, grounded in your diff, checked through seven validation gates, and reviewed by you. It's free for public repos, costs about $0.004 per doc PR, and never touches your code — it just opens PRs you approve.

If you've read this far, you already know your README is lying about something right now. The cheapest way to find out is to merge one small PR with docs-keeper installed and see what it opens.

See a real docs PR it opened · Install free on GitHub

Try it on one repo.

Free for public repos · grounded in your diff · you review every PR.

Install free on GitHub

Keep reading

Liked this? Get the next one.

Occasional posts on docs, AI, and keeping documentation in sync with code. No spam, unsubscribe anytime.