58,158 files / 405.865 GiB in 27:56; 994 MiB added, 77 MiB stored -- the one-time full re-read cost I/O, not storage, as predicted. Also records a false alarm I raised against my own instrument: `restic ls <snapshot> <path>` reported 0 of 40 archived memories because restic 0.18.0's path filter does not recurse into subdirectories. The unfiltered listing shows all 40. An instrument that can drop results silently is not a measurement -- and this one was measuring a safety property.
9.0 KiB
REPORT — instruction trim part 2: felhom.eu, the memory index, the versioned workspace
2026-08-06. Closes R-229 legs (a) and (c). Opens R-230 and R-231.
Full per-block accounting: documentation/audits/LEDGER-instruction-trim-part2-2026-08-06.md.
Parallel session. A second Claude Code session was live in these repos throughout. Per
CLAUDE.md's rule this report is aREPORT-<topic>.mdsibling; the sharedREPORT.mdwas not touched.
1. Baselines
felhom.eu c21bcf84f709 · felhom-controller 7db42c5fec3b · felhom-agent 062a7027abff —
all clean, all HEAD == origin/main at start. Only felhom.eu was written to.
2. felhom.eu/CLAUDE.md
| before | after | |
|---|---|---|
| raw lines | 252 | 130 |
| effective lines | 227 | 115 |
| bytes | 16,698 | 7,824 |
Split into a core plus .claude/rules/{hub,website,manifests,docs}.md — 4 files, all
paths:-scoped, all ≤60 effective lines. instructions_gate is registered in
scripts/repo_gates.py: six gates, all OK. Order was load-bearing — trim first, register second,
because a registered-but-failing gate refuses every push through .githooks/pre-push.
Two blocks were kept in the core against §6's sketch, both to avoid rebuilding the failure class
they prevent: register discipline (applies to every session, not only documentation/** ones)
and the R-110 installer fence (triggered by scripts/felhom-host-install.sh, which no fixed glob
matches). Ledger §C.
3. Hook evidence for Scenario B — both directions
Two fresh sessions, so the negative control cannot be explained by prior loading:
| Run | File read | hub.md |
website.md |
|---|---|---|---|
| A | website/index.html |
— | path_glob_match |
| B | hub/internal/api/handler.go |
path_glob_match |
— |
Why fresh sessions were required — the session's most useful negative result: after creating
.claude/rules/, in-session reads that should have matched produced no hook line at all. A
directory whose instructions were already seeded is not re-scanned. A rule file can be correct, pass
every gate, and reach the model never, purely because of when it was created.
4. Always-loaded total
Measured /context (operator-supplied, this session): Memory files held at 11.5k tokens across
three rule loads while Messages grew 8 → 105.7k. The 11.5k baseline is unchanged by this work,
because MEMORY.md moved 17,688 → 17,977 bytes (+1.6%) — the reconciliation was net-neutral by
design, trading 40 archived entries for 4 indexed ones plus trimmed detail. A fresh session is needed
for a post-change token reading; the byte figures above are exact.
5. Memory reconciliation
Backup: /mnt/5_hdd/felhom.eu/backups/claude-memory-20260806-103418 (158 files, taken first).
| before | after | |
|---|---|---|
.md files |
158 | 158 — zero deletions |
| orphaned | 44 | 0 |
| indexed / archived | 113 / — | 117 / 40 |
MEMORY.md |
145 ln / 17,688 B | 150 ln / 17,977 B |
Headroom: 50 lines and 7,622 bytes (29%) against the 200-line / 25 KB limits. The
index-vs-archive discriminator was the store's own type: field: all 4 reference orphans indexed,
all 39 project + 1 untyped archived.
6. Staleness diagnosis — diagnosed, not fixed (R-230(a))
- 21 lines carry component version literals
- 5 lines carry bare host addresses
nodes.mdowns - 3 expired temporal statements; 2 undated open items
The structural finding, not the counts: felhom-agent/CLAUDE.md had its expired
TEMPORARY … until ~2026-08-02 block deleted in part 1 and the gate now fails any such block — while
MEMORY.md still asserts "demo boxes REMOTE till ~08-02". The contradiction was moved, not
resolved: the hand-written half is clean, the auto-written half — which is larger and loads every
session — still states the retired fact.
7. Secrets scan
535 keyword-matching lines across 104 files; 1 key: value-shaped hit, which is prose; 0
private-key blocks. No credential values. Nothing from the store was committed regardless.
8. Gates
repo_gates.py --fast → 6 gates, all OK, rc=0. Test suite 20 → 39 assertions, 0 failures.
Red-proof against the real store, not a fixture — ceiling 200 → 100:
memory index : 150 lines (ceiling 100), 17977 bytes (ceiling 25600)
instructions_gate: 1 FAILURE(S)
- /mnt/5_hdd/felhom.eu/git/.claude-memory/MEMORY.md: 150 lines, ceiling 100. Content past the
auto-memory limit is DROPPED WITH NO ERROR — a truncated index is a silent failure with no
observable. …
Ceiling restored; gate rc=0 and suite 39/0 again.
9. Installer
Idempotency: run 2 reported "nothing to do" and settings.json sha was identical before and
after — the claim is "changed nothing the second time", not "ran twice without erroring".
Merge safety: sha256 of settings.json minus .hooks was f1775e94aa9d1ca9 before and after
the write; all 7 top-level keys, 30 permission entries, 3 plugins, effortLevel, tui and
additionalDirectories survived byte-identically.
10. Backup coverage — positive observable
From the journal of the real unit:
Including Claude auto-memory store: /mnt/5_hdd/felhom.eu/git/.claude-memory
start backup on [/mnt/4_hdd/data /mnt/5_hdd/felhom.eu/git/.claude-memory]
Completed and verified in the repository. Snapshot b587f775, 58,158 files / 405.865 GiB in
27:56, 994.132 MiB added (77.440 MiB stored) — 77 MiB stored for a 405 GiB re-read confirms the
one-time cost was I/O, not storage. Contents: 161 .claude-memory entries — 118/118 top-level
.md and 40/40 under archive/. The 03:07 snapshot holds /mnt/4_hdd/data only, so the two
snapshots are a clean before/after in one listing.
A false alarm I raised against my own instrument. The first check used
restic ls <snapshot> <path> and reported 0 of 40 archive files — which read exactly like 40
memories silently unprotected. It was wrong: restic 0.18.0's path filter lists entries at that path
and does not recurse. The unfiltered listing shows all 40. Recorded because it is this project's own
rule — an instrument that can drop results silently is not a measurement — biting a measurement made
to verify a safety property.
Three caveats that make this weaker than "backed up" sounds: the destination is on the same
physical disk as the store; the DooPlex backup set has no off-site leg
(sync-hetzner-backups.sh is jarrs.eu and pulls the other way); and /opt/backup/scripts/ is itself
unversioned host state (R-231).
11. Rules report — first run
8 events; session_start 4, path_glob_match 3, nested_traversal 1. 6 of 9 rule files had never
fired. Not a defect list: the log only covers since the hook was armed, and
felhom-controller/gates.md proves the distortion — it fired at 08:11 the same day, before
installation, and reads as silent.
The hook now self-rotates at 5 MB, one generation.
12. context7 — nothing written
plugin:context7:context7 is ✘ failed (ENOENT on npx -y @upstash/context7-mcp); /mcp lists
only the six Google auth stubs. Per §5 that means write nothing. The Go code is not the use case
either: one direct dependency in felhom-agent, and the hub and controller are stdlib-only by
standing rule.
12b. A stale claim the checklist caught — corrected in all four repos
Confirming this session's own push by run ID (checklist's last item) surfaced that four instruction
files claimed CI was still owed (R-168) — and R-168 was CLOSED on 2026-08-02. CI exists, runs
repo_gates.py --fast on every push, and emails on failure; this session's commits produced runs
117 (success) and 118.
One of the four was felhom.eu/CLAUDE.md, where this session carried the stale sentence forward
through the trim. felhom-agent/CLAUDE.md contradicted itself — its release section already
said R-168 mails the failure. Corrected in all four, pushed.
Two lessons about this task, not about CI: a trim is a volume operation and carries stale content forward unless each claim is re-checked; and the gate cannot catch this class — "this register item is closed" is not mechanically checkable from the instruction file. The thing that caught it was the checklist item demanding a run ID rather than a memory.
13. Registers
- R-229 re-scoped — legs (a) and (c) CLOSED; (b) remains; (d) moved to R-230.
- R-230 opened — the auto-written-staleness ruling, the symlink decision, the spec-as-failing-test pilot.
- R-231 opened —
/opt/backup/scripts/is unversioned host state. - S-36 added to
CONTEXT.mdstanding rulings.
14. Observations — not acted on
target-selection.md's t740 error; felhom-agent/CLAUDE.md at 173 effective lines; the
/context Messages-accounting inference (§A of the ledger — reasoned, not measured); MEMORY.md's
header still reading "felhom-controller Project Memory" though it indexes all four repos.