A CLAUDE.md That Survived a Refactor Badly
Six months ago, a team wrote this into their project's CLAUDE.md:
## Authentication
All authentication logic lives in `middleware/auth.py`. Token
validation happens there before any route handler runs. If you need
to change how tokens are validated, that's the only file to touch.
Since then, the team migrated from a monolithic middleware file to a
per-route dependency-injection pattern: token validation now happens
in auth/dependencies.py, injected individually into each FastAPI
route via Depends(get_current_user). Nobody updated the CLAUDE.md.
- Walk through what happens when a new contributor asks Claude Code to "add rate limiting to the token validation step," with this stale instruction still in place.
- Explain why this failure mode is more dangerous than simply having no instruction about authentication at all.
- Propose a process (not just "update the file") that would make this kind of staleness less likely to reach this point undetected.
1. What happens with the stale instruction in place:
Claude reads "all authentication logic lives in middleware/auth.py"
as a confident, specific claim about the codebase — exactly the kind
of concrete, verifiable-sounding instruction that's supposed to be
trustworthy. It has no independent signal telling it this claim is
six months out of date. So it very plausibly opens middleware/auth.py
(which may still exist, perhaps now empty, deprecated, or repurposed),
tries to add rate-limiting logic there, and either: adds dead code
that never executes because nothing calls that file's validation path
anymore, or gets confused when the file doesn't contain what the
instruction describes and has to spend effort reverse-engineering the
real structure anyway — effort the instruction was supposed to save.
In the worst case, it partially succeeds in a way that looks plausible
(rate-limiting code that compiles and even runs) but never actually
gates the real per-route validation in auth/dependencies.py, shipping
a security feature that silently does nothing.
2. Why this is worse than no instruction at all:
With no instruction, Claude would default to actually investigating —
grepping for Depends(get_current_user), tracing how routes wire up
auth, and finding the real, current structure. It would take a bit
longer, but would very likely land on the truth. A confident, specific,
wrong instruction actively short-circuits that investigation: it
gives Claude a plausible, verifiable-looking answer that happens to be
false, and specificity is exactly what makes an instruction more
likely to be trusted and acted on without independent verification.
Stale-but-precise information is more dangerous than absent
information because it looks like the trustworthy kind of entry
CLAUDE.md is supposed to contain, and preempts the very investigation
that would have surfaced the real (correct) location.
3. A process to catch this before it reaches six months stale:
"Just update the file" doesn't scale because nobody is naturally prompted to remember CLAUDE.md exists during a refactor unless it's made part of the refactor's definition of done. A few concrete, durable practices:
- Treat CLAUDE.md like code for review purposes: any PR that moves,
renames, or restructures something CLAUDE.md references by path
should be expected to grep for that path (or filename) across
CLAUDE.mdand.claude/rules/as part of the PR, the same way you'd grep for other callers before removing an export. This can even be a lightweight CI check — a script that extracts backticked paths mentioned in CLAUDE.md and fails the build if any no longer exist on disk (this specific case, a path rename, is mechanically detectable even without full semantic understanding). - Prefer instructions that reference stable concepts with the path
as a secondary detail, and periodically re-verify rather than
assuming permanence — e.g. phrase it as "auth logic is centralized
in one place (check for
Depends(get_current_user)usage or an equivalent) — as of [date/version], that'sauth/dependencies.py" rather than a bare unconditional path claim, so a reader (human or agent) is nudged to sanity-check rather than trust blindly forever. - Periodic audit, independent of any specific refactor: treat a stale-CLAUDE.md check as part of a recurring housekeeping pass (e.g., alongside dependency updates), since some refactors won't touch the exact grep-able strings CLAUDE.md uses and won't be caught by tooling.
The underlying point is that CLAUDE.md rot is a silent failure mode — it doesn't break a build or fail a test — so the fix has to be a process that surfaces it despite that silence, not a one-time cleanup.
Share this question