You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
scripts/derive_counters.py --check is a strong gate on absolute totals in five named files, but two whole classes of counter prose sit outside it. A wrong counter can be written into CHANGELOG.md or a CLAUDE.md release note and pass CI green, every time.
This surfaced concretely in #993: the changelog and the CLAUDE.md release note both claimed commands 147 → 150 for a plugin that adds exactly one command. The gate passed. It was caught by a human/bot reviewer reading the prose, not by CI.
Reproduction
Appending a fabricated delta to CHANGELOG.md and running the gate on an otherwise clean, correct tree:
Green, with skills 999 → 1234 sitting in the changelog.
The two gaps
1. File coverage. The gate reads README.md, root CLAUDE.md (only its Current Scope / Status: lines), marketplace.jsonmetadata.description, mkdocs.ymlsite_description, and .codex-plugin/plugin.json descriptions. CHANGELOG.md is not read at all, and CLAUDE.md's release-note sections — which carry a counter line per release — are outside the two checked lines.
2. Claim shape.CLAIM_PATTERNS recognises seven fixed phrasings of an absolute total:
Nothing matches a delta of the form X → Y, in any file — including the five that are checked. Since release notes and changelog entries state deltas rather than totals by convention, the dominant form of counter prose in this repo is the one form the gate cannot see.
Token-budget figures (2,101 / 4,000 tokens) are outside the vocabulary too, and the comma grouping would defeat (\d+) even if a pattern existed. #993 shipped CLAUDE.md claiming 2,066 on one line and 2,101 twelve lines later, about the same file.
Why deltas are the higher-risk form
An absolute total is wrong only if it is wrong. A delta is a pair — old → new — and stays silently wrong when only one half is updated. Both #993 defects were exactly that:
147 → 150: the branch merged dev, the baseline moved 147 → 149, and three of the four figures on the line were updated while commands was carried through. A half-rebased delta.
2,066 → 2,101: the number was correct when measured, then a later commit changed the file and one of the two copies was updated.
Neither is a typo. Both are the predictable result of hand-editing a derived number in more than one place.
Sketch of a fix
Roughly in order of value per effort:
Add a delta pattern — recognise <counter> A → B (and A -> B), then assert B equals the derived value. A can't be checked against the tree (it's a historical baseline), but asserting the right-hand side alone would have caught 147 → 150.
Widen file coverage to CHANGELOG.md and all of CLAUDE.md, not just its two headline lines.
Generate rather than validate. The reviewer on feat(engineering): compile OpenAI's Spinning Up in Deep RL into a knowledge-base plugin #993 put it as: assert counters from a single generated source rather than copy-pasting across files. A derive_counters.py --write that rewrites the marked spans, with --check verifying the rendered output, removes the drift class instead of detecting it.
Option 1 alone closes the specific hole that shipped. Option 3 is the real fix and is a larger change.
Prior art in this repo
The gate already grew twice in response to live escapes — the multi-occurrence scan was added after PR #985 shipped a README banner saying 380 while a section heading still said 370, and the badge and per-domain-table checks were added for the same reason. This is the same failure mode one level out: correct derivation, hand-typed prose.
Scope note
Deliberately not fixed in #993. That PR was a skill compilation plus an emitter bug fix; changing the repo-wide counter gate inside it would have widened the diff well past its subject. Filing separately as agreed.
Summary
scripts/derive_counters.py --checkis a strong gate on absolute totals in five named files, but two whole classes of counter prose sit outside it. A wrong counter can be written intoCHANGELOG.mdor aCLAUDE.mdrelease note and pass CI green, every time.This surfaced concretely in #993: the changelog and the
CLAUDE.mdrelease note both claimedcommands 147 → 150for a plugin that adds exactly one command. The gate passed. It was caught by a human/bot reviewer reading the prose, not by CI.Reproduction
Appending a fabricated delta to
CHANGELOG.mdand running the gate on an otherwise clean, correct tree:Green, with
skills 999 → 1234sitting in the changelog.The two gaps
1. File coverage. The gate reads
README.md, rootCLAUDE.md(only itsCurrent Scope/Status:lines),marketplace.jsonmetadata.description,mkdocs.ymlsite_description, and.codex-plugin/plugin.jsondescriptions.CHANGELOG.mdis not read at all, andCLAUDE.md's release-note sections — which carry a counter line per release — are outside the two checked lines.2. Claim shape.
CLAIM_PATTERNSrecognises seven fixed phrasings of an absolute total:Nothing matches a delta of the form
X → Y, in any file — including the five that are checked. Since release notes and changelog entries state deltas rather than totals by convention, the dominant form of counter prose in this repo is the one form the gate cannot see.Token-budget figures (
2,101 / 4,000 tokens) are outside the vocabulary too, and the comma grouping would defeat(\d+)even if a pattern existed. #993 shippedCLAUDE.mdclaiming2,066on one line and2,101twelve lines later, about the same file.Why deltas are the higher-risk form
An absolute total is wrong only if it is wrong. A delta is a pair —
old → new— and stays silently wrong when only one half is updated. Both #993 defects were exactly that:147 → 150: the branch mergeddev, the baseline moved 147 → 149, and three of the four figures on the line were updated whilecommandswas carried through. A half-rebased delta.2,066 → 2,101: the number was correct when measured, then a later commit changed the file and one of the two copies was updated.Neither is a typo. Both are the predictable result of hand-editing a derived number in more than one place.
Sketch of a fix
Roughly in order of value per effort:
<counter> A → B(andA -> B), then assertBequals the derived value.Acan't be checked against the tree (it's a historical baseline), but asserting the right-hand side alone would have caught147 → 150.CHANGELOG.mdand all ofCLAUDE.md, not just its two headline lines.derive_counters.py --writethat rewrites the marked spans, with--checkverifying the rendered output, removes the drift class instead of detecting it.Option 1 alone closes the specific hole that shipped. Option 3 is the real fix and is a larger change.
Prior art in this repo
The gate already grew twice in response to live escapes — the multi-occurrence scan was added after PR #985 shipped a README banner saying 380 while a section heading still said 370, and the badge and per-domain-table checks were added for the same reason. This is the same failure mode one level out: correct derivation, hand-typed prose.
Scope note
Deliberately not fixed in #993. That PR was a skill compilation plus an emitter bug fix; changing the repo-wide counter gate inside it would have widened the diff well past its subject. Filing separately as agreed.