Repository navigation
S-7: the briefing prints a line labelled 'rot' that is not the rot check — R7 fired all night, unseen #29
Description
What happened
This morning's briefing on the home PC read, in part:
19 repos clean · nothing unpushed · no remote cache (Stage 2)
That line sounds like a rot verdict. It is not one. The rot check had not run.
Run by hand, seconds later, it reported:
$ mindforge rot
R7 MEMORY.md older than the file(s) it indexes: project_kognogos_dev_server.md project_next_session_plan.md
That finding was true all night. It is the direct upstream cause of the stale
briefing the maintainer gave the work-laptop session this morning (see S-6). It was never
printed, because nothing printed it.
The defect, exactly
session_start is 37 lines. It calls headline. It never calls rot:
$ awk '/^session_start\(\)/{f=1} f{print} f&&/^}/{exit}' bin/mindforge | grep -nE "rot|R[0-9]"
(no matches)
rot has exactly two callers — brief (line 222) and the rot) CLI branch
(line 685). Both are things a human types. Neither fires at session start,
which is the one moment the check exists to serve.
The part that makes it a trap rather than a gap
Inside headline, at line 74:
# rot line
local dirty=0 unpushed=0 n=0
for r in "$PROJECTS"/*/.git; do
...That block counts dirty working trees and unpushed commits. It is repo
hygiene, not rot_checks(). R1–R8 are never evaluated.
But it is commented # rot line, and it renders as 19 repos clean · nothing unpushed — a clean verdict, in the voice of the check, in the briefing
slot where the check's output would go. Every reader of that briefing, human or
assistant, takes it as "the rot check ran and found nothing."
This is #24 / S-2 — a check that cannot run reports a negative — with one
turn of the screw. There, a check that could not run reported clean. Here, a
check that was never invoked at all has its name printed above a clean
result produced by something else.
How rarely it actually runs
~/.claude/state/rot.log, entire contents:
2026-08-29 R2 nog root PKGBUILD source differs from AUR
2026-08-29 R2 nog root PKGBUILD diverges from AUR on validpgpkeys
2026-08-29 R4 memory has 42 files but MEMORY.md indexes 38
2026-08-30 R2 nog root PKGBUILD source differs from AUR
2026-08-30 R2 nog root PKGBUILD diverges from AUR on validpgpkeys
2026-09-16 R8 queue says mindforge is next on #13, but #13 is CLOSED
2026-09-18 R7 MEMORY.md older than the file(s) it indexes: …
Four days of findings in three weeks, every one because a human typed brief or
rot. The 2026-09-17 go-live session — six hours, twelve commits, the session
in which all of this rotted — has no entry at all. The check never ran while
the rot was happening.
Why this is worth fixing before the backlog fixes anything else
R1–R8 are good rules. Every finding in that log is real and was worth knowing.
The rules are not the problem; the trigger is. A check that fires only when
somebody suspects a problem cannot catch the class of problem where nothing looks
wrong — which, per METHOD.md's own line, is the entire point:
Things that break announce themselves. Things that rot do not — nothing fails.
What "fixed" would look like
Not a decision. Three parts, separable:
session_startrunsrot_checksand prints findings. The obvious fix.
Cost: R8 asks a real tracker over the network, so session start becomes
network-dependent — which is presumably why it was left out. Mitigation
already exists:queue-refs.cache, and R8 already reportsUNKNOWNrather
than inventing a clean result.- Split the trigger from the cost. Run R1–R7 (local, fast) at every session
start; leave R8 tobrief, or serve it from cache and say so. - Rename the
# rot lineblock and its output, whatever else is decided.
While a line labelled with the check's name is produced by something that is
not the check, every fix above still gets read as already working.
(3) is cheap, independent of the others, and is the part that actually misled a
session this morning.
Related
- S-2: a check that cannot run reports a negative — three times in one session #24 / S-2 — a check that cannot run reports a negative. Same family.
- S-6 — the rolling handoff nothing writes. R7 caught it; this issue is why
nobody saw that it had. - S-5: wrap declares completion without verifying it — two false wraps in one session #27 / S-5 —
wrapdeclares completion without verifying it.
Activity
test: an automated harness for the closeout path (step 0 of #22/#27/#28/
jetomev commentedon Sep 29, 2026
Fixed in
01e721b, pushed tomain(unreleased; next release). All three parts of "what fixed would look like" are done.1. session-start runs the rot check. It prints
rot check (R1-R9):and the findings inside the<session-briefing>tags, so they reach the assistant at every launch. The directive now says: "Name every rot-check finding in it; 'nothing rotting' is the only line you may summarise." Findings go torot.logfrom session start too, so three-strikes counts sessions rather than the times someone typedbrief.2. The cost, measured rather than assumed. R8 was one
ghcall per queue reference, in series: 2.2s cold for the four references here, and offline each waits out its 8s timeout, so 32s before a session could start.prefetch_refsnow asks about every uncached reference at once; R8 then reads only the cache, and a miss is reportedNOT verified(never silence, per #24). On a copy of the real state: session-start 1.1s cold, 0.6s warm.MINDFORGE_GH_TIMEOUT(default 8) is the offline worst case. R8 was kept at session start rather than split off: R8 is the rule that caught #13, and the parallel lookup made it affordable.3. The impersonation. The headline block is no longer commented
# rot line, and its output readsgit: 22 repos clean · nothing unpushed. It says what it measures, and the real rot verdict sits under its own label directly below it.First real run (on a copy of the state) reported a true finding: the queue still names a closed issue as a project's next task. Exactly this issue's case, on day one.
Tests. 9 new checks: the section is printed, sits inside the tags, carries an R8 finding, the directive asks for findings, rot.log is written, an unreachable tracker is
NOT verified, three 2s lookups finish under 5s and are all cached, the git line is labelled. 8 fail against the previous code. The ninth, "all three were cached", passed before too, since caching already existed. The timing check first ran on session-start and passed on the old code for the wrong reason (it never asked the tracker at all). It now timesrot, which the old code ran serially. Full suite 123 checks · 123 passed.fix(session-start): run the rot check where it matters, and stop impe…
docs: README + version bump for v0.1.5 (nothing silent)