BUGS
bugs/vanishing-addresses/record.md
sha256 2ebfcd6e75c07775 · 4086 bytes ·
original held in the private archive
---
title: The Vanishing Addresses
status: SEALED
repo: archive
observed: 2026-07-20
caught_by: measuring the same history query with and without --no-renames and getting two different answers
one_line: Doc pages were named by content hash, so every edit moved a page's address and every regen dropped the old one — and the tool that mined the old addresses silently under-counted them.
guard_location: museum/generate.mjs · redirect ledger · lines ~310 and ~1741; migrateRedirectLedger()
verified: fresh + bare-derived clones regenerate identically; a redirect stub confirmed resolving to its new nested page over HTTP
exhibited: true
lesson: A content-derived address moves on every edit and rots every inbound link; and a history-mining mechanism can silently miscount — always pass --no-renames when mining git log --diff-filter=A over similar-shaped generated files.
provenance:
- commits 43390e6 + 0f3d846 (this archive)
- Claude memory record museum-path-based-naming-and-redirects
- museum/redirect-ledger.json (the committed ledger)
---
## What happened
Document pages were named `docs/<sha256(rawBytes)[:12]>.html` — the generator
content-hashed its **inputs**, so **every edit moved the page's address**, and every
regeneration dropped the old one. Across this repo's own history, **389 distinct hash
addresses** have existed. Worse, and this is the second half of the specimen: the first
mining pass to recover the old addresses found only **305** of them. Git's
content-similarity rename detection was pairing unrelated generated pages as "renames" of
each other — they share large boilerplate around a short doc body — and hiding the rest
from `--diff-filter=A`. A mechanism built to lose nothing was itself quietly losing 84
addresses.
## How it was caught
The address-instability was caught by the owner-facing naming review. The **miscount**
was caught by measuring the same history query with and without `--no-renames` and getting
two different answers (305 vs the true 389).
## The guard
Two guards. **(1)** Pages are named from the document's **archive path**, mirroring the
archive's directory tree — the address only moves if the document itself is renamed. A
runtime `seenSlugsLower` check aborts the build if two paths ever fold to the same address
case-insensitively (Windows checkouts fold case). **(2)** Every old hash address ever
published is frozen in a committed `redirect-ledger.json`, mined once with `--no-renames`
on both `git log` calls; each old address whose document still exists gets a
meta-refresh redirect stub at the old path, every build. HTML stubs (not Cloudflare
`_redirects`) so old bookmarks resolve even on a raw `file://` open.
## What the guard costs
A 389-entry ledger committed forever, plus a redirect stub file per live old address in
every build — paid so that no link anyone ever saved can rot. The `--migrate-redirects`
ritual is a maintained code path (though it should not need to run again, since path-based
naming produces no new hash addresses).
## Containment
Verified per the project standard: fresh GitHub clone **and** bare-clone-derived clone,
both `git status` clean at stamp `80bf9bb · f64fb7397980c9cb`, byte-identical to each
other and to two back-to-back local regenerations; 4,986 internal links checked, 0 broken;
a redirect stub confirmed resolving to its new nested page when served over HTTP. One gap
recorded honestly by the source: a `file://` click-through of the meta-refresh itself was
**not** separately re-verified by screen automation that session (relying on meta-refresh
being a protocol-agnostic HTML mechanism) — worth a real desktop-browser check next time.
## Provenance
Commits `43390e6` + `0f3d846`; `museum/redirect-ledger.json`. Full account, including the
owner's three-option choice and the `--no-renames` lesson, in the Claude memory record
`museum-path-based-naming-and-redirects`. Mockup specimen 06 corroborates (its figures
"390 / 306" are off by one from the memory record's audited "389 / 305"; this record uses
the memory record's numbers).
STAMP · generated for RELEASE v2.8.5 commit 06e5180 (06e51801b38a) · archive input-tree hash c07fbfbdd2e1ddeb · 754 files · no wall-clock timestamp (regenerates identically when nothing changed).