Skip to content

S-7: the briefing prints a line labelled 'rot' that is not the rot check — R7 fired all night, unseen #29

Closed

Description

@jetomev

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:

  1. session_start runs rot_checks and 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 reports UNKNOWN rather
    than inventing a clean result.
  2. Split the trigger from the cost. Run R1–R7 (local, fast) at every session
    start; leave R8 to brief, or serve it from cache and say so.
  3. Rename the # rot line block 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

Activity

  1. jetomev commented on Sep 29, 2026

    @jetomev
    OwnerAuthor

    Fixed in 01e721b, pushed to main (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 to rot.log from session start too, so three-strikes counts sessions rather than the times someone typed brief.

    2. The cost, measured rather than assumed. R8 was one gh call 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_refs now asks about every uncached reference at once; R8 then reads only the cache, and a miss is reported NOT 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 reads git: 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 times rot, which the old code ran serially. Full suite 123 checks · 123 passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions