BUGS
bugs/crlf-page-rename/record.md
sha256 c2c988ebbe5dc2f3 · 3104 bytes ·
original held in the private archive
---
title: The Carriage-Return Rename
status: SEALED
repo: archive
observed: 2026-07-20
caught_by: fresh-clone regeneration churning pages on a machine with autocrlf=true
one_line: The generator content-hashes raw bytes, so a CRLF checkout didn't just add \r — it renamed every generated page and re-churned the whole museum.
guard_location: .gitattributes (eol=lf, deliberately not -text); sync.ps1 writes LF, not CRLF
verified: with LF pinned, a fresh clone regenerates without churn; proven from a clone, not a local re-run
exhibited: true
lesson: When addresses are derived from bytes, line endings are load-bearing; pin them at the source and on checkout, and prove it from a fresh clone.
provenance:
- commits 2f4848c (.gitattributes) and 5bc7137 (sync.ps1)
- Claude memory record museum-lf-normalization
- root CLAUDE.md "Archive-specific invariants" table
---
## What happened
The machine's system-level gitconfig sets `core.autocrlf=true`. Because
`museum/generate.mjs` names each doc page `docs/<sha256(rawBytes)[:12]>.html` — hashing
its **inputs** — a CRLF checkout does not merely add `\r` characters: it **renames every
generated page** and re-churns the entire museum. Separately, `sync.ps1` had been writing
its generated manifests with PowerShell's default CRLF (`WriteAllLines` uses
`Environment.NewLine`), which baked CRLF hashes for `_CAPTURE_MANIFEST.txt`,
`_CAPTURED_STORES.txt`, `ARCHIVE_RETAINED.md`, and `AUDIT.md` into the committed museum.
This is the same failure family as [[vanishing-addresses]]: an address that moves for a
reason unrelated to the content's meaning.
## How it was caught
A fresh clone on the autocrlf machine regenerated with the whole museum churning —
addresses moving with no content change.
## The guard
Repo-root `.gitattributes` forces `* text=auto eol=lf` (with `*.png`/`*.zip binary`),
overriding the system `autocrlf`. It is deliberately `eol=lf` (text) and **not** `-text`:
`-text` would break the line-readable publication diff the archive depends on.
`sync.ps1` was rewritten to emit LF at the source, so the same rename bug cannot be
reintroduced upstream of checkout.
## What the guard costs
Every text file in the repo is pinned to one line ending regardless of platform
convention, and both surfaces (`.gitattributes` on checkout, `sync.ps1` on write) must
stay correct — two places that must agree forever.
## Containment
Proven from a **fresh clone**, not a local re-run: a local working copy is already LF, so
a local regen would hide the bug entirely (the same trap as [[untracked-stowaway]]). With
LF pinned, a fresh clone regenerates without churn. This invariant is listed in the root
`CLAUDE.md` "do not revert" table.
## Provenance
Commits `2f4848c` (`.gitattributes`) and `5bc7137` (`sync.ps1`). Full account in the
Claude memory record `museum-lf-normalization`. Not among the eight approved wall
specimens, but recorded because it is a real, richly-documented archive defect that
formalised a standing invariant; whether it joins the wall is a curation/ordering call
for the generator and the owner.
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).