Skip to content

Comments no longer cite line numbers in other files, and a suite refuses new ones (#191) - #197

Merged
fdaviddpt merged 6 commits into
mainfrom
fix/191
Aug 18, 2026
Merged

fdaviddpt merged 6 commits into
mainfrom
fix/191

Conversation

@fdaviddpt

@fdaviddpt fdaviddpt commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Closes #191. Rebased onto 5b46095.

The defect

A <file>.sh:NNN pointer is exact the day it is written and wrong on the next pull request that inserts a line above it. It rots silently: a rotted citation reads exactly like a live one, so the reader follows it and lands on a plausible-looking comment rather than on an error. That is this repository's own defect class pointed at its own source.

The rot rate is measured, and it was measured twice

At 98386f1 there were nine citations of that shape outside the assembled changelog: six wrong, one drifted, two right.

At 5b46095 — this branch's rebase target, hours later — there were eleven: seven wrong, one drifted, three right. #194 added the two new ones, in tests/test-session-markers.sh, a file this change does not otherwise touch. One of the two was already wrong on the day it was written, its line number landing three lines past the sentence it names.

CI is what found them, and the check that found them is the one this PR adds. The citations this sweep first went red on were not the ones it was written for. That is the argument for a check rather than a cleanup, and it is a measurement rather than a prediction.

What replaces a line number

A function name, a distinctive literal, or the issue number. All three are greppable, all three survive an insertion above them, and the issue number additionally says why rather than where. The trade is taken knowingly — none is as precise as a line. I found no citation where the number was genuinely the only useful pointer; every one named something with a name.

One of the two new ones wanted more than a rename. the comment at session-start-hook.sh:25-29 is a claim about two hooks agreeing rather than a pointer to one site, so it now names the pair of functions the claim is about and says which half of it that test leg proves.

The check

tests/test-line-citations.sh. Three outcomes, not two:

  • enforced — every tracked shell file under scripts/ or tests/. A hit fails the suite.
  • advisory — every other tracked file. A hit is printed in full and does not fail.
  • not sweptCHANGELOG.md, stated rather than silently skipped: it is assembled by .oss/assemble_changelog.py and never hand-edited, so a finding in it is unactionable by construction.

The false-positive surface was costed before the check was written, and re-measured on the rebase. 98386f1: 96 basenames, 10 hits, 0 false. 5b46095: 98, 12, 0. Every hit both times was a real citation. These shapes were checked as non-matching and are driven as controls the suite fails on:

awk: cmd. line:3                 no basename before the colon
scripts/common.sh: line 7        bash diagnostic form
In scripts/common.sh line 7:     shellcheck form
line 7 of scripts/common.sh      the recommended prose form
mycommon.sh:7                    a longer basename is not a reference to a shorter one

One honest residual, documented and deliberately not allowlisted: grep -n output quoted verbatim in a comment would match. The escape hatch is prose, because an allowlist rots at exactly the rate the numbers do.

Counts are pinned to a commit rather than written in the present tense. The rebase is what proved that was right: the pin went stale in under a day and said so, where a present-tense number would have read true and been wrong. Both measurements are kept, not replaced.

One thing for you to decide

The advisory half was advisory because tooling.md was held by PR #192, and reddening a file this branch may not edit is how a check gets disabled in week one. #192 has since landed, so that reason is spent. I did not widen it: that is unrequested scope on a PR in review, and the question it settles — whether every future doc, template, example and jit-context entry is bound by this rule, in every installed project's contributor path — is a design decision I was not briefed to make.

Both the suite and CLAUDE.md now say that in as many words, so a spent reason cannot read as a live one. The cost if you want it: two awk alternations in the selectors, plus one line in tooling.md, which is the single remaining advisory finding. Measured false positives on that surface: zero, twice.

The suite cannot pass vacuously

Three controls run before a single real file is read, and the first exit 1s rather than printing a sweep nobody performed: a planted bare-basename citation must be seen; a planted path-qualified one must be seen (the dir-prefix branch is otherwise never driven, since every real citation is bare-basename); the five forms above must not match. A needle that fails to compile lands on the same branch. Both file sets carry a floor guard, and an unreadable tracked path is counted rather than skipped in silence.

Review

The auditor found four real defects in the first commit — the advisory set had no floor guard where the enforced set did, the enforced selector was flat-only, unreadable paths were dropped silently, and the dir-prefix branch had no control. All four fixed.

One reviewer finding was argued down: it recounted the audit as 5-wrong-not-6 and read the tooling.md citation as correct on the strength of that entry's own sentence "The truncation is pre-tool-hook.sh:127-144 and it is correct". That "it" is the truncation behaviour being deliberate, not the range being accurate — and the same review judged the identical range wrong when cited from scripts/jit-dry-run.sh.

Also

README.md said "Two suites are the exception" and this makes it three. CLAUDE.md carries the convention.

Verified by the maintainer

CI caught what neither of us could have locally, and it caught it with this PR's own check. The ubuntu leg failed on two citations in tests/test-session-markers.sh that PR #194 wrote today, in a file this branch did not contain, in the window between the branch point and the review. One of the two was already wrong the day it was written. That is the rot rate measured twice — nine citations at 98386f1, eleven at 5b46095 — rather than asserted, and it is better evidence for this change than any fixture.

The argued-down finding is the one I checked by hand, before the rebase. The reviewer read tooling.md's "The truncation is pre-tool-hook.sh:127-144 and it is correct" as the entry vouching for its own range. I read the sentence in the tree: that clause is about the truncation behaviour being deliberate — the following clause says "what was wrong is that the row read as enforced" — and the same review called the identical range wrong when cited from jit-dry-run.sh. Refusing it was right.

Red re-run, mine, against main with only the suite copied in: exit 1, 3/4, six real stale citations, all three controls green in the same run. A needle that failed to compile would have gone quiet; the controls are what make the red mean something.

Costing the false-positive surface before writing the check is why it will survive — 96 basenames, whole tree, 10 hits, 10 real, 0 false, and zero again after the rebase. The grep -n-quoted-in-a-comment residual stays in prose rather than in an allowlist, and I agree with that: an allowlist rots at the rate the numbers do.

The scope call handed back to me is answered in #198, not here. Widening the advisory half now costs two awk alternations and one line in tooling.md — but it binds every future doc, template, example and entry in every installed project's contributor path, and that belongs in its own change with its own red, not as a rider on a PR already reviewed. What this branch does instead is the part that could not wait: the spent reason no longer reads as a live one, in both the suite output and CLAUDE.md.

Force-pushed by me after the rebase, 1dc43bf7cd0237, with --force-with-lease. A rewrite, on a branch that is this loop's own, opened twenty minutes earlier, that nobody else has fetched — and the prior head is still reachable from the PR timeline. Recorded because a force-push should never be silent, not because it was in doubt.

Florian DAVID and others added 6 commits August 18, 2026 16:54
…ching (#191)

A `<file>.sh:NNN` pointer is exact the day it is written and wrong on the next
PR that inserts a line above it. It rots silently: a rotted citation reads
exactly like a live one, so the reader follows it and lands on a plausible
comment rather than on an error.

Counted on main at 98386f1, outside the assembled changelog: nine citations of
that shape. Six pointed at the wrong thing, one had drifted off the block it
named, two were still right. #191 names three of the six; the sweep added here
found the other three. #185 added one of them three hours before #190 moved it.

Every citation in an enforced file now points at something greppable -- a
function name, a distinctive literal, or the issue number, which additionally
says why rather than where.

tests/test-line-citations.sh has three outcomes rather than two. It FAILS on a
citation in a tracked scripts or tests shell file; it REPORTS WITHOUT FAILING
over every other tracked file; and it names the assembled changelog as
deliberately not swept, because that file is generated and a finding in it is
unactionable. The advisory half is advisory rather than enforced for one stated
reason: its only remaining finding lives in an entry held by PR #192, and
reddening a file this change may not edit is how a check gets disabled in its
first week. Widening it is one awk pattern.

The false-positive surface was costed before the check was written: over all 96
tracked basenames and the whole tree, 10 hits and 0 false. A bash diagnostic, a
shellcheck line, an awk error and a longer basename are driven as controls that
must not match, alongside a planted citation that must.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ver had (#191)

Four findings from the self-review, all in tests/test-line-citations.sh unless
noted:

- The advisory sweep had no floor guard on its file count, so a broken selector
  would print `Clean across 0 file(s)` and exit 0 -- byte-identical, to
  run-all.sh which reads only the exit code, to a genuinely clean tree. It now
  takes the same `< 10 -> exit 2` floor the enforced sweep already had.
- The enforced selector was `[^/]*`, so a shell file landing in a subdirectory
  one day would fall silently into the report-only bucket and stay there. Now
  `.*`, which closes the shape rather than half of it.
- A tracked path that is not a readable file was skipped in silence in both
  loops, while the header above claimed a count the sweep never reached. Both
  are counted now; an unreadable path in the enforced set fails the suite.
- The needle accepts an optional directory prefix, and no enforced file
  exercises that branch -- every real citation was bare-basename -- so a dead
  prefix branch would have read as clean. It has its own positive control now.

scripts/jit-dry-run.sh named `jit_log_name()` and `jit_row_id()` as being "in
pre-tool-hook.sh". They are defined in common.sh and only called there, which
is an anchor wrong in the same way the line numbers were. Reworded to what is
true: pre-tool-hook.sh routes every refused row through them.

The 96-basename and 10-hit figures are now pinned to 98386f1 in all three
places that carry them, rather than stated in the present tense. They were
already stale by two the moment this branch added a file, which is the exact
failure mode the change is about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- CLAUDE.md read as attributing the move to #191 itself, because the issue
  citation sat immediately after "the PR that moved it". #185 added it and #190
  moved it three hours later; the sentence now says so.
- scripts/jit-dry-run.sh: "which #7 is the reason for" -> "which exists because
  of #7". Accurate either way, clunky one way.

A third finding was argued down rather than applied. The reviewer recounted the
audit as 3 right / 1 drifted / 5 wrong and read the tooling.md citation as
correct, on the strength of that entry own sentence "The truncation is
pre-tool-hook.sh:127-144 and it is correct" -- where "it" is the truncation
BEHAVIOUR being deliberate, not the line range being accurate. The same range
in scripts/jit-dry-run.sh was judged wrong in the same review, which is the
contradiction: at 98386f1 those lines are the #182 subagent_type comment, and
the truncation is at 162-185. The count stands at 6 wrong / 1 drifted / 2 right.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ree (#191)

The test section enumerates the suites that are about this repository rather
than the hooks, and the enumeration was a count. Adding tests/test-line-citations.sh
made the sentence wrong in the quietest possible way -- a reader counting two
and finding three has no reason to think anything is missing.

Named rather than counted where it can be: the third one is called out by name
and by what it refuses, and the "assembler they are about" clause now says
which two it means.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…pen (#191)

CI caught these, and the check that caught them is the one this branch adds.
Neither is a defect in this diff -- they are in tests/test-session-markers.sh,
section J, which #194 (issue #177) added to main after this branch was cut.

- `session-start-hook.sh:32 parses the payload with jit_json_fields +
  jit_session_key` -- the line number was ALREADY WRONG on the day it was
  written: line 32 is mid-sentence in the LC_ALL paragraph, and the parse claim
  is three lines earlier. The comment names its own functions, so the number
  was carrying nothing the prose did not already carry. Dropped.
- `the comment at session-start-hook.sh:25-29` -- accurate today, and a pointer
  to a comment rather than to code, which is the shape most likely to move.
  This one wanted more than a rename: it is a claim about TWO hooks agreeing,
  so it now names the pair of functions the claim is about and says which half
  of the claim this leg proves.

The counts pinned to 98386f1 are re-pinned to 5b46095 in all three places that
carry them, and the second measurement is kept beside the first rather than
replacing it. Nine citations at 98386f1, eleven at 5b46095; 96 basenames / 10
hits / 0 false, then 98 / 12 / 0. Pinning was already the right call and the
rebase is what proved it: the pin went stale in under a day and SAID SO,
where a present-tense number would have read true and been wrong.

That interval is now the argument the suite header leads with. The citations
this check first went red on in CI were not the ones it was written for, they
were written by somebody else while it was in review, and one of the two was
born broken. A cleanup would not have caught either.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A reason that has been spent reads exactly like a reason that still holds --
which is the defect class this whole branch is about, in the file that exists
to refuse it. The suite printed "advisory only while #192 holds one of the
files it would flag" on every run, and #192 merged at eb9cf71.

Replaced with what is true: widening is a scope decision nobody has taken, not
a blocked one. Deliberately NOT widened here. It is unrequested scope on a pull
request already in review, and the question it settles -- whether every future
doc, template, example and jit-context entry is bound by this rule, in every
installed project's contributor path -- is a design decision this branch was
not briefed to make. Filed for the maintainer with the exact cost instead: two
awk alternations in the selectors, one line in the entry currently flagged,
and zero measured false positives on that surface across two commits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cross-file line-number citations into pre-tool-hook.sh are already wrong, and every PR makes them wronger

1 participant