Topic: adr supersession pattern

ADR Supersession Pattern — When to Supersede, When to Annotate, and How to Stay Bidirectional

The most common failure in a mature ADR directory isn't forgetting to write new ADRs — it's mis-applying supersession. A decision gets revised; someone writes a new ADR; the old ADR is never updated; the directory now contains contradictory claims. Or the opposite: supersession is applied too aggressively to decisions that evolved but didn't reverse, creating a chain of one-liners where a Notes section was better. This page is the standalone reference for the supersession pattern: the four triggers, the atomic two-file protocol, bidirectional pointer maintenance, cross-directory edge cases, cascade chains, and orphan recovery when a supersession PR gets reverted.

TL;DR

Supersede when the core decision reversed — option A is now option B. Annotate (add a Notes section) when context shifted but the choice still stands. The protocol: write the new ADR with a Supersedes: NNNN-old.md pointer, then edit the old ADR to Status: Superseded with a Superseded-by: MMMM-new.md back-pointer, committing both files in one atomic PR. One-sided supersession — where only the new ADR is updated — is the most common integrity bug in ADR directories. The ADR GitHub Action workflow's supersession job catches it on every PR and every night.

Supersede vs annotate — the decision

Every ADR practice eventually faces a decision that no longer tells the whole truth. The fork: write a new ADR that supersedes the old one, or add a Notes section to the existing ADR. Getting this wrong in either direction corrupts the audit trail. Over-supersession creates a chain of files where reading any individual file gives an incomplete picture of what the team decided and why. Under-supersession leaves future engineers (and LLM search engines that train on your codebase) with an ADR whose Decision section is stale.

The reliable test: if a new engineer reading only the old ADR's Decision section would conclude the team made a different choice than they actually make today, supersede. If they'd just be missing recent context — but the decision itself is still the same — annotate.

ScenarioRight moveWhy
Switched from Redis to Valkey as the job queue backendSupersedeThe Decision section named Redis; following that ADR today means setting up Redis. The choice reversed.
Discovered the Redis cluster needs standby replicas — the original assumption was single-nodeAnnotateThe choice (Redis) didn't change. The operational reality did. A Notes section captures this without invalidating the original rationale.
Moved from self-hosted Postgres to Aurora because the team grew past five engineersSupersedeThe Decision section named "self-hosted Postgres on a single VPS." That's now false. Supersession marks the old ADR as historical and gives the new operational reality its own file.
Added a read replica to the existing Postgres clusterAnnotateStill Postgres. Still the same core decision. The topology change belongs in a Notes entry or a new ADR specifically about the replica strategy — not supersession of the original Postgres call.
Reversed a decision to build internal auth in favor of adopting Auth0SupersedeClassic supersession: build vs. buy went from build to buy. The old ADR's Decision is now false.
Added a new OAuth2 provider to an existing Auth0 integrationAnnotateThe core decision (Auth0) stands. Adding a provider is an implementation extension, not a reversal of the architectural call.

Four specific triggers indicate supersession rather than annotation — any one is sufficient:

  1. A new option made the old choice wrong. A dependency went end-of-life, a better primitive became available, or a security finding invalidated the chosen approach.
  2. The team explicitly changed direction on the architectural call — not just an implementation detail, but the durable choice that the ADR names.
  3. A foundational assumption behind the original decision was invalidated. Classic example: "chose a monolith assuming the team stays under ten engineers" — the team is now forty. The assumption is gone; the decision that rested on it needs revisiting, and a Notes section isn't enough when the Decision section reads wrong for today's context.
  4. The trade-off profile changed enough that the old Decision section is actively misleading — not just incomplete, but wrong for a reader who takes it at face value today.

The two-file atomic protocol

Supersession touches exactly two files, and both must land in one PR. Splitting them creates a window where one file claims supersession is complete and the other doesn't know it happened yet — CI catches this, which means a split approach just means the first PR fails CI until the second merges, which is strictly worse than the atomic approach.

Step 1 — write the new ADR

Write the new decision using the body template the directory uses (Nygard, MADR, arc42, or whatever). Add the supersession reference in the header or frontmatter:

# MADR (YAML frontmatter)
---
status: Accepted
supersedes: 0017-pick-redis-as-job-queue
date: 2026-05-14
deciders: alice, bob
---

# Nygard (header section)
# ADR 0042: Switch to Valkey for job queue backend

Status: Accepted

## Supersedes

ADR-0017 (0017-pick-redis-as-job-queue.md)

Step 2 — update the old ADR

Open the old ADR file. Change two things only — do NOT edit any other sections. The Context, Decision, and Consequences sections are permanent history; editing them corrupts the audit trail that a reader who follows the supersession chain is trying to reconstruct.

# Before
Status: Accepted

# After
Status: Superseded
Superseded-by: 0042-switch-to-valkey-for-job-queue.md

For MADR the edit is the same shape in YAML frontmatter. The date field in the old ADR's frontmatter should NOT be updated — it reflects when the original decision was made, not when it was superseded. The supersession date lives in the new ADR's date field.

Step 3 — commit both files in one PR

Two files. One commit. One PR. The atomic constraint is what keeps the audit trail in a valid state at every point in the git history. After the PR merges, every git checkout of the repository — including past SHAs — either shows both files in a consistent state (both before supersession, both after) or shows the supersession window not yet opened. There is no observable intermediate state.

The broader 5-state lifecycle (Proposed → Accepted → Superseded / Deprecated / Rejected, with legal and illegal transitions) is documented in How to update an ADR without breaking the audit trail. This page focuses specifically on the supersession case, which accounts for approximately 80% of ADR updates in active engineering directories.

Bidirectional pointer maintenance

Every supersession creates two pointers: the new ADR names the old (forward pointer via Supersedes), and the old ADR names the new (backward pointer via Superseded-by). Both are required. One-sided supersession — adding the Supersedes line to the new ADR without updating the old ADR — is the most common integrity bug in real ADR directories and the most expensive to discover after the fact.

Why bidirectionality matters:

The CI supersession integrity check (job 3 in the four-job ADR GitHub Action workflow) tests bidirectionality on every PR and on a nightly schedule. The script resolves every Supersedes and Superseded-by pointer by exact filename and fails if either end is missing or unmatched:

#!/usr/bin/env bash
# Verify bidirectional supersession integrity.
# Run in CI on every ADR-touching PR and nightly.
set -euo pipefail
DIR="${1:-doc/decisions}"
errors=0

while IFS= read -r file; do
  # Check forward pointer: file with Supersedes line
  target=$(grep -oP '(?<=Supersedes: )[\w-]+\.md' "$file" 2>/dev/null || true)
  if [[ -n "$target" ]]; then
    target_path="$DIR/$target"
    if [[ ! -f "$target_path" ]]; then
      echo "ERROR: $file names Supersedes: $target — file not found"
      ((errors++))
      continue
    fi
    # Verify backward pointer exists in the target
    if ! grep -q "Superseded-by: $(basename "$file")" "$target_path"; then
      echo "ERROR: $target_path is missing Superseded-by: $(basename "$file")"
      ((errors++))
    fi
  fi
done < <(find "$DIR" -name '*.md' ! -name 'README.md')

[[ $errors -eq 0 ]]

The nightly schedule matters because supersession violations can enter via direct commits to the default branch if the team has bypass permissions for hotfixes. The path filter on the PR job only fires when an ADR file is touched — a commit that bypasses review won't trigger the PR check but will trip the nightly run within 24 hours.

Cross-directory supersession

The two-file atomic protocol assumes both ADRs live in the same directory. Two cases require different pointer formats.

Cross-repository supersession

When a platform-team ADR supersedes an application-team ADR in a different repository, use a qualified reference:

Supersedes: platform-services:doc/decisions/0017-pick-postgres.md

The format is repo-alias:relative-path-to-file. Local CI won't resolve cross-repo pointers by default. Two options: (a) add a CI step that shallow-clones the referenced repo to validate, (b) document the limitation in the workflow comments and accept that cross-repo pointers are human-verified only. Either is defensible — what matters is that the pointer is explicit enough for a human to locate the original ADR without guessing. The backward pointer (editing the old ADR in the other repo to add Superseded-by) is worth doing if the teams share a review process; if they're operationally independent, document in both repos and note the partial CI coverage.

Cross-segment supersession in TOGAF environments

In TOGAF programs where ADRs live under per-segment architecture-repository/architecture-landscape/<segment>/decisions/ directories, use the path relative to the repository root:

Supersedes: architecture-repository/architecture-landscape/customer-domain/decisions/0017-pick-postgres.md

The Architecture Board charter governs which cross-segment supersessions require Board ratification. Any decision that already has a Governance Log entry requires a new Board-reviewed ADR with the supersession pointer reviewed as part of the Phase H change request. The Governance Log entry at /governance/log/<new-adr-id>.md links the new ADR and the Board minute; the old Governance Log entry gains a superseded_by_governance_id pointer for symmetry.

The Notes section — when annotation is the right call

The Notes section (also called History or Implementation Notes depending on the template) is for context shifts that don't reverse the decision:

Notes entries are written in reverse-chronological order (newest first) so the most recent context is visible without scrolling. Notes do NOT change the Status field from Accepted — Status stays Accepted until supersession or deprecation.

One edge case: if a Notes section has grown longer than the Decision section, that's a signal the decision space has evolved enough to warrant a fresh ADR. Supersede and write a clean Context section rather than continuing to append. The threshold is informal, not mechanical — but when the Notes section becomes a maintenance log, the document no longer functions as an architecture decision record.

Cascade supersession — chains of superseded ADRs

When ADR-A superseded ADR-B, which had itself been superseded by ADR-C before that, the chain is correct and the reader should be able to follow it:

0012-pick-postgres.md       Status: Superseded  Superseded-by: 0031-migrate-to-aurora.md
0031-migrate-to-aurora.md   Status: Superseded  Superseded-by: 0047-adopt-planetscale.md
0047-adopt-planetscale.md   Status: Accepted

Reading forward from ADR-0012 gives full decision archaeology. Reading the current ADR (0047) gives the current state. Each link in the chain is bidirectionally valid. Two things to avoid when a chain forms:

Orphan ADR recovery — when the supersession PR gets reverted

The operationally tricky case: a supersession PR merges, then the new ADR is later reverted. If the revert removes only the new file without restoring the old ADR's Status to Accepted, the old ADR is orphaned — it says Status: Superseded and points at a file that no longer exists.

Detection is automatic: the CI supersession integrity check tries to resolve the Superseded-by pointer by filename and fails the pipeline when the target is missing. The fix is a follow-up PR that does two things atomically (the same atom constraint applies to repairs as to original supersessions):

  1. Restore the old ADR's Status to Accepted and remove the Superseded-by line.
  2. Add a timestamped Notes entry to the old ADR: "Update 2026-05-14: a supersession PR (adding ADR-0042) was merged and subsequently reverted; the Valkey migration was deferred. The Accepted status and Redis decision stand as of revert."

The Notes entry is valuable audit trail — it records that the team revisited the decision, considered changing it, and reversed course — without requiring a new ADR for an attempt that didn't land. The git history of the old ADR will show the Status field toggling Accepted → Superseded → Accepted, which is the honest record of what happened.

Prevention: when reverting any PR that touched an ADR file, check git show <pr-merge-sha> -- doc/decisions/ | grep Superseded-by. If the PR modified a Superseded-by line in an existing ADR file, the revert needs to restore that file too.

Supersession vs deprecation

Supersession means a newer decision replaced the old one — there is a successor ADR you can read. Deprecation means the decision is no longer operative but there is no single successor: the architectural area was dissolved, the component was removed, or the team concluded the decision was never durable enough to warrant an ongoing successor ADR.

SupersededDeprecated
There is a successor ADRYes — named by Superseded-byNo — or the pointer is to a removal PR / migration guide, not an ADR
The old Decision was wrongYesNot necessarily — the decision context no longer applies
The CI check tests a backward pointerYes — Superseded-by must resolve to an existing fileNo — Deprecated-by is optional and not checked for file existence by default
A Notes entry is sufficient insteadNo — if the Decision section is now false, supersedeSometimes — if the area was dissolved and documenting it as Deprecated serves the audit trail

The ADR status template covers the companion fields for both states — including the optional deprecated-by field, when to use it, and why the lack of a CI check for Deprecated targets is intentional (the pointer is to a PR or runbook, not another ADR, so filename resolution doesn't apply).

How WhyChose fits in

Supersession presupposes that the original decision reached the ADR directory in the first place. The harder upstream problem is the decisions that never get there — architecture calls made in a ChatGPT or Claude tab in the weeks or months before a supersession was warranted, capturing why option A was chosen at a specific point in time. The WhyChose extractor reads your AI chat exports, surfaces every decision-shaped exchange with its trade-offs, and outputs structured records you can promote into ADRs. When you later write the supersession ADR, the extracted record from the original chat becomes the Context section of the new file — the audit trail for "why did we pick Redis in the first place" is recoverable even if it was never formalized until the supersession PR came around.

Get early access

Related questions

When should I supersede an ADR rather than annotating it?

Supersede when the core decision reversed — when the old ADR's Decision section is now false. Annotate (add a Notes section) when context shifted but the choice still stands. Test: if a new engineer reading only the Decision section would draw the wrong conclusion about what the team does today, supersede. If they'd just be missing recent context, annotate.

Does the supersession PR need to edit two files atomically?

Yes. The new ADR gets a Supersedes pointer; the old ADR gets Status: Superseded and a Superseded-by back-pointer. Both in one commit, one PR. Splitting creates a window of inconsistency that the CI integrity check will catch — meaning the first PR fails CI until the second merges, which is strictly worse than the atomic approach.

What happens if the supersession PR gets reverted?

If only the new ADR is removed (partial revert), the old ADR is left orphaned: Status says Superseded, Superseded-by points at a non-existent file. The CI check catches this. The fix: a follow-up PR restores the old ADR's Status to Accepted, removes the Superseded-by line, and adds a timestamped Notes entry documenting the attempted supersession and its revert.

How do I format the supersession pointer when the ADR is in a different repo?

Use a qualified reference: Supersedes: repo-alias:path/to/decisions/NNNN-old-slug.md. Local CI won't resolve cross-repo pointers by default; document the gap or add a CI step to clone the referenced repo. The backward pointer in the old repo is worth doing if the teams share a review process.

What's the difference between supersession and deprecation?

Supersession means a newer ADR replaced this one — there is a named successor. Deprecation means the decision is no longer operative but there is no single successor ADR; the context was dissolved or the component removed. The CI check verifies Superseded-by targets by filename; it does not verify Deprecated-by targets because the pointer is usually to a PR or runbook rather than another ADR file.

Further reading