Skip to content

Pin CLAUDE.md's pytest module count to len(testpaths), red-first #171

Closed

Description

@JC-000

This is a decision to record, not a defect to fix. Nothing is broken today.
An offer was made in a PR body, nobody answered it, and it will be lost when
that PR merges. This issue exists so the answer is "no, and here is why" or
"yes, do it" rather than silence.

The offer

While auditing documentation drift, PR #168 was asked to add a pin to
tools/test_pytest_boundary.py asserting that the module count stated in
CLAUDE.md equals len(testpaths). The author declined, and closed the
argument with:

Consequently there is no red-first test to show. If you would rather have the
count-agreement pin anyway, say so and I will write it red first — I just do
not think it earns its brittleness.

— PR #168, "The pin: I did not add one, and here is why".

No reply was posted. The offer is still open and is currently buried in a PR
description.

What the pin would be

tools/test_pytest_boundary.py (222 lines, 5 assertions) already proves — by
AST, in both directions — that pytest.ini's testpaths is exactly the set of
tools/test_*.py modules pytest can run cleanly:

  • test_every_testpath_exists
  • test_testpaths_lists_every_runnable_tools_module
  • test_testpaths_lists_nothing_pytest_cannot_run
  • test_rig_dirs_hold_no_pytest_named_files
  • test_norecursedirs_covers_every_rig_dir

The proposal adds a sixth: locate the count stated in CLAUDE.md's prose and
assert it equals len(testpaths) read from pytest.ini.

The case FOR

Issue #161's comment thread sets the repo's standing rule for exactly this
class. Its framing: two copies of a fact, only one of them real — an assertion
that "passes because something coincides with the truth, and nothing
distinguishes coincidence from cause." The prescription:

check whether the second copy would actually change if the first one did

and, where it would not:

Where a fact can be mechanically pinned, pin it instead of restating it.

The thread cites two in-tree instances as the pattern to follow — the proposed
.assert LIB_NISTCURVES_REU_BANK_COMB = $02 (#120) and build/flags.stamp
(#159) — both of which "replace a sentence someone must remember with a build
error."

A prose module count fails the triage question outright: it does not change when
testpaths changes. And the rot is not hypothetical. At master right now:

  • pytest.ini lists 6 entries in testpaths.
  • CLAUDE.md:606 says "pins testpaths to the three pure-logic modules" and
    CLAUDE.md:608 says "Bare pytest at root: 31 passed."
  • README.md:442-445 repeats "the three modules" and "31 passed", and
    README.md:452 adds "31 passed, 74 fixture errors".

Three stale statements of one fact, in two files. That is the defect the pin
would have caught.

The rot is also fast. #168's own body records the count as "five modules and
41 tests"
at the time it was written, and notes six/47 was then the state of
branch fix/unresolvable-test-hostname. That branch merged (#164, 0b55c30),
so the correct figure moved from five to six during the PR's own review.
Any number written into prose here has a shelf life measured in days.

The case AGAINST

Taken from #168's argument, which is a real one and should not be waved off.

1. Pinning a number inside prose has no non-brittle failure mode. The pin
must locate the count with a pattern. On a reword such as "pins testpaths to
the pure-logic modules"
there are exactly two behaviours available:

2. #168 arguably discharged the risk already, and did it better. Rather than
pin the duplicate, that PR deletes it. On branch docs/drift-audit-161,
CLAUDE.md:634-641 now reads:

pytest.ini pins testpaths to the pure-logic modules — that list is the
enumeration; read it there, not here
— and tools/test_pytest_boundary.py
proves it is exactly the set pytest can run, in both directions. […] A bare
pytest at the root is green; the total is not quotable, because it tracks
testpaths and the build state (test_uci_data_acc.py skips its cases
without a UCI PRG in build/). Run it rather than citing a number.

README.md gets the same treatment. #161's rule is "pin rather than restate";
not restating at all is the stronger form of not restating, and it leaves
nothing for a pin to pin. If #168 merges as written, this issue is moot until
someone re-adds a count
— which is itself the thing to decide: whether to
guard against that reintroduction, or accept that a reviewer catches it.

3. The pass-count half cannot be pinned at all. 41 is build-state
dependent: tools/test_uci_data_acc.py skips its cases when build/c64-https.prg
or build/labels.txt is absent or is not a BACKEND=uci image
(tools/test_uci_data_acc.py:583-584, :591, :714-720). No single number is
correct even at a fixed commit. Only the module count is pinnable; the more
frequently-quoted number is not.

4. The negative form does not work either. #168 considered forbidding any
doc from stating a bare pytest pass count, and rejected it: docs/engineering-notes.md
is the measurement record, and its dated 25 passed, 75 errors line is a
legitimate historical fact. No regex separates "dated historical measurement"
from "present-tense claim".

What is actually being decided

  1. Does the repo want a mechanical guard against a count being reintroduced
    into CLAUDE.md / README.md prose, or is deletion plus review sufficient?
  2. If a guard is wanted, is a count-agreement pin the right shape, or is a
    narrower "no bare pytest pass count in present tense" rule (with its
    acknowledged regex problem) closer to the real invariant?
  3. Whichever way, docs/engineering-notes.md's dated historical counts must
    survive it.

If accepted, the terms

The author offered to write it red-first, and that offer should be taken
literally:

References

Activity

  1. JC-000 commented on Sep 8, 2026

    @JC-000
    OwnerAuthor

    Answered: no pin — the second copy was deleted instead.

    The pin proposed here was "assert the count stated in CLAUDE.md's prose equals len(testpaths)". #168 removed the count, so there is no longer a target to assert against. CLAUDE.md:899-905 now says the opposite of a count:

    pytest.ini pins testpaths to the pure-logic modules — that list is the enumeration; read it there, not here … Run it rather than citing a number, and build BACKEND=uci first.

    That satisfies #161's rule by the cheaper of its two remedies. A pinned count is two copies plus machinery to keep them equal; no count is one copy. tools/test_pytest_boundary.py keeps its five assertions, which are the ones that carry real weight — they pin the set, in both directions, and that is the fact worth mechanising.

    What this gives up, stated plainly: nothing prevents a count being re-added to CLAUDE.md later, after which it drifts silently again. The only guard covering that would be an inverted one — assert CLAUDE.md contains no literal module count — which is a prose-scanning regex over a file that changes constantly. It would false-positive on unrelated numbers and get muted, and a muted guard is worse than none. Not worth its brittleness, which was the original author's position in #168.

    Caveat worth recording, because a live counter-example turned up the same day this was closed: CLAUDE.md carried two contradictory figures for the uci-comb CRYPTO_OVERLAY tail (153 B vs 126 B, once contradicting itself two lines apart; measured 126 B, corrected on fix/triage-quickwins). That is the same restated-fact drift this issue is about — but that number comes out of a build map, not a config file, so neither deletion nor a cheap equality pin fixes it. It needs re-measurement discipline, which CLAUDE.md's Memory-layout maintenance note already demands. Closing this should not be read as "restated facts are fine now": the pytest count was just unusually easy to delete.

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