Use git history to catch stale ADRs before they mislead agents
A stale ADR is worse than no ADR when an AI agent is reading it as truth. In an architecture decision records workflow, the failure mode is not “we forgot to document the decision”; it is “the document still looks authoritative after the system moved on.” That is how an agent confidently reimplements yesterday’s architecture because the Markdown file was never told it was yesterday’s architecture.
The uncomfortable part is that missing ADRs are easier to spot than misleading ones. A blank folder invites caution. A polished decision doc with a clear title, rationale, and status invites trust. If the code has drifted but the ADR has not, the repo now contains false certainty.
A stale ADR is worse than no ADR when agents are reading it as truth
The old model of architecture decision records was simple: write down why you chose X so future humans do not argue from memory. That still matters. But once AI coding agents are in the loop, the cost of a stale ADR changes shape. The agent does not just read the doc; it uses it as a source of truth while planning edits, generating patches, and deciding which files matter.
That means stale ADRs can actively mislead work. If an ADR says “service B owns the payment schema” and ownership moved six months ago, an agent may keep editing the wrong boundary with perfect confidence. The result is not just documentation debt. It is wrong code, wrong tests, and wrong review guidance.
Google’s guidance on ADRs is explicit that decisions should be revisited as systems evolve, and the canonical ADR repository treats status as part of the record rather than an afterthought. That is the right instinct, but it is not enough on its own. A markdown status field does not update itself when the code changes.
wiki freshness scoring helps here because freshness is not a nice-to-have metadata field; it is the mechanism that keeps documentation from masquerading as current. If you already maintain an internal wiki, the same rule applies to ADRs: they need a freshness signal, not just a creation date.
STALE ADR FAILURE MODE
The practical principle is blunt: if code and decision disagree, the decision is stale until proven otherwise. Not deprecated. Not “probably fine.” Stale.
The commit patterns that usually mean an ADR has drifted
The maintenance signal is git history correlation. Not vibes. Not “someone on the team remembers changing that.” If an ADR governs a module, interface, or contract, the question is whether the files and symbols it points at have changed enough to require review.
This is where high-churn repos get dangerous. At something like 200 commits a week, drift hides fast. A decision can stay untouched for months while the code around it is rewritten by small, reasonable commits that never trigger a formal architecture review.
The invalidation patterns are usually boring, which is why they work:
- Co-change bursts. The same ADR-governed files keep changing with adjacent modules. That suggests the boundary the ADR described is no longer stable.
- Interface changes. Public methods, HTTP routes, protobufs, event names, or config surfaces change. If the ADR was about a contract, the contract has moved.
- Ownership turnover. The people touching the files change, or the bus factor shifts. An ADR that once matched a clear owner can become fiction when a new team owns the path.
- Hotspot churn. A module keeps appearing in churn × complexity analysis. That is often a sign the original decision is under pressure.
- Repeated exceptions in commit messages. If the log keeps saying “temporary workaround,” “compat layer,” or “bridge until migration,” the ADR should stop pretending the old path is canonical.
git history correlation is the right lens because it turns these signals into a review rubric instead of a memory test. You do not need perfect certainty. You need a repeatable trigger that says, “this decision deserves another look.”
blast radius and ownership signals is useful here because drift is rarely isolated. Once a path starts accumulating dependents, reviewers, and co-change partners, the architectural cost of being wrong rises quickly.
ADR DRIFT SIGNALS
A useful rule of thumb: if the ADR’s governed files have changed materially in the last few weeks and the decision has not, assume drift before assuming stability.
How to link architecture decision records to the code they govern
The workflow gets much better once an ADR is not just a markdown file, but a record linked to specific paths, symbols, and contracts. An architecture decision records workflow should answer three questions for every ADR:
- What files or modules does this decision govern?
- What API surfaces or symbols does it constrain?
- What commit signals should force a review?
That mapping can be explicit. For example:
adr/0012-cache-layer.md→src/cache/,CacheClient,cache-controlheadersadr/0021-event-schema.md→events/*.proto, topic names, consumer groupsadr/0034-auth-boundary.md→middleware/auth.py,AuthContext, session tokens
Once you have that, git history becomes a maintenance signal instead of a pile of noise. A commit touching events/*.proto is not just code churn; it is a potential decision invalidation if the ADR claims the schema is stable.
A tool or system that can surface path-based decisions and file-linked context makes this much easier to operationalize. For example, a decision lookup that returns the ADR, the governed paths, and the current freshness state lets reviewers ask, “is this still true?” before they ask, “should I trust this doc?”
That is the role of path-based decision lookup in a mature workflow: not to archive philosophy, but to answer whether a file still has an active architectural rationale attached to it.
file-linked architecture context matters too, because the decision should be retrieved alongside the symbols and owners it touches. If an ADR says “service-local cache is the source of truth,” the context should show the owning files, the related callers, and whether the surrounding module has changed shape.
We got this wrong initially by treating ADRs as standalone artifacts. That was convenient, and wrong. The fix was to bind them to code surfaces, then make review a function of movement in those surfaces.
ADR TO CODE MAP
Repowise’s decision intelligence is built around that idea: decisions linked to graph nodes, staleness-tracked, and queryable through get_why(). That kind of path-aware retrieval is what keeps an agent from treating every ADR as equally current.
A review checklist for deciding whether an ADR is still valid
The review question is not “is this ADR old?” Age alone is a weak signal. The question is: what changed in the code, and does that change invalidate the decision?
Use a checklist like this during PR review or a weekly architecture hygiene pass:
- Does the ADR still map to the same files, modules, or contracts?
- Have any governed files had repeated edits in the last few weeks?
- Did the public interface, schema, or dependency boundary change?
- Are there new co-change pairs that suggest the boundary has shifted?
- Has ownership moved to a different team or set of reviewers?
- Do recent commits describe exceptions, workarounds, or compatibility layers?
- Does the ADR still describe the current source of truth, or only a historical choice?
When code and decision disagree, default to marking the ADR stale. Then choose the next action based on impact:
- Amend when the original decision still holds but the rationale or scope needs updating.
- Supersede when a new decision replaces the old one and the old path should be clearly retired.
- Archive when the decision is only historical and no longer guides active code.
The distinction matters. Amend means “same decision, corrected context.” Supersede means “new decision, old one no longer governs.” Archive means “keep it for history, but stop presenting it as active guidance.”
A stale ADR becomes harmful rather than merely outdated when agents or reviewers are likely to use it as a current rule. That usually happens when the doc still matches the repo’s shape at a glance, but not its behavior. If it can misdirect a patch, it is harmful.
Here is a simple review table you can actually use:
| ADR title | Governed files | Recent git signals | Drift assessment | Action taken |
|---|---|---|---|---|
| Cache ownership boundary | src/cache/, cache_client.py | 11 co-changes with src/session/, 3 interface edits | Boundary drifting | Supersede |
| Event schema versioning | events/*.proto, consumer docs | 4 schema bumps, 2 compatibility shims | Contract moved | Amend |
| Image upload pipeline | upload/*.py, routes/images.ts | Low churn, same owner, no contract changes | Stable | Keep |
| Legacy metrics adapter | metrics/legacy/ | Repeated workaround commits, no active callers | Harmful if surfaced by default | Archive |
Worked example: flagging a stale ADR from git history before it misleads an agent
Suppose an ADR from last year says:
“The billing service owns invoice formatting. All consumers should read formatted invoices from
billing/api.”
That was true when the service was small. Then the code changed.
| Item | What the ADR says | What git history shows | Drift signal | Review outcome |
|---|---|---|---|---|
| ADR title | Billing invoice formatting | Same title, no edits in 9 months | Frozen decision text | Review |
| Governed files | billing/api/, billing/formatters.py | 14 commits touching billing/, 6 touching invoices/renderer.py | Co-change burst | Review |
| Contract surface | GET /invoices/:id response shape | 3 commits add invoice_v2, 2 compatibility shims | Interface change | Supersede |
| Ownership | Billing team | Recent commits by platform team | Ownership turnover | Review |
| Commit messages | None | “temporary adapter,” “migrate consumers,” “keep old path for now” | Repeated exceptions | Harmful if trusted |
Before the review, an agent asks how to add a new invoice field. It reads the ADR, assumes billing/api is canonical, and starts editing the wrong layer. After the review, the ADR is marked stale, then superseded with a new decision that points to invoices/renderer.py as the active formatting boundary.
That is the whole point of a stale-decision warning: stop the agent before it treats an old architecture as truth.
If you want to automate this kind of review, get_why() can surface path-based decisions, while get_context() can show the file-linked architecture context around a target. get_risk() then tells you whether drift is local noise or a boundary with real blast radius. Those three views together are enough to turn “I think this ADR is stale” into something a lead can act on.
FAQ: should old ADRs ever be archived?
How do you know when an architecture decision record is stale?
When the files, symbols, or contracts it governs have changed enough that the decision no longer describes the code. The strongest signals are repeated co-changes, interface or schema edits, ownership turnover, and commit messages that mention compatibility layers or temporary workarounds.
Should old architecture decision records be archived or superseded?
Archive when the decision is historical only. Supersede when a new decision replaces it and the old one could still be mistaken for current guidance. If the old ADR is still reachable, make sure its status is explicit so agents do not treat it as active.
What git history signals show that an ADR no longer matches the code?
Look for co-change bursts around the governed files, contract changes in APIs or schemas, churn in a hotspot module, and repeated exception commits. In a repo moving at 200 commits a week, those signals matter because drift accumulates faster than human memory.
How do you keep AI coding agents from following outdated ADRs?
Do not let retrieval treat all ADRs equally. Freshness, linkage to files, and status should affect what gets surfaced first. If a decision is stale or superseded, keep it out of the default path and return current context instead of old authority.


