Merged
Conversation
…ree siblings, not just with the same parser (#177) session-start-hook.sh:32 reads session_id with jit_json_fields + jit_session_key out of common.sh -- the same functions pre-prompt, pre-tool and pre-path use -- and the comment above it says a second, simpler regex would be a second answer to "what is a session id". Those three pin LC_ALL=C. This one did not, so the sentence was a claim the code did not carry out. Measured at 98386f1, 3 engines x 2 locales, session_id carrying a lone 0xE9: one-true-awk C, gawk C, mawk C, mawk UTF-8 "" refused, correct one-true-awk en_US.UTF-8 "" + a suppressed towc diagnostic gawk en_US.UTF-8 the id ACCEPTED, 0xE9 and all gawk in a multibyte locale does not match a lone 0xE9 against [^A-Za-z0-9_-], so the bare-name check stopped being one -- on the awk that is awk on most Linux boxes and on ubuntu-latest. Bounded honestly: the separators are single-byte and still matched, so nothing left the state directory. What was lost was agreement. tests/test-session-markers.sh section J drives all three engines through a shimmed rm. The obvious fixture -- plant the marker the unpinned parse would clear -- cannot be built on macOS, because APFS refuses a file name that is not valid UTF-8; the first draft was red on all three engines for that reason alone. Asking what rm was handed answers on every filesystem. Every negative leg is paired with a positive control in the same fixture. The NUL-drop in the command substitution on the same line was measured rather than argued about: no NUL reaches the capture on any engine in either locale, so it costs nothing. README.md said LC_ALL=C was pinned on every awk in the plugin. It was not and still is not -- nine invocations under scripts/ carry no pin. The sentence now claims only what holds: every awk that reaches the pattern guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found by the audit of d066595, in the test that commit added. tests/test-session-markers.sh already carries the third state -- section I sets SKIPPED_SECTIONS and run-all.sh keeps exit 2 apart from both a pass and a failure. Section J did not use it. On a host with no UTF-8 locale it printed a SKIP-NOTE and exited 0, which is byte-identical to a run that DID drive the one cell the section exists for: gawk under a multibyte locale. That is this repository own defect class, in the test written to close an instance of it. Five sibling suites share the note-and-exit-0 convention. Not changed here -- they are not this diff, and flipping them is a CI decision about the Windows leg rather than a fix. Driven, all three states: UTF-8 locale present rc=0 57 passed, 0 failed locale shimmed to report US-ASCII rc=2 section J SKIPPED, named same + JIT_TESTS_REQUIRE_UTF8_LOCALE=1 rc=1 a failure outranks a skip The summary line is fixed in the same edit: the symlink clause was unconditional, so an H-only skip already rendered as "0 section(s) SKIPPED (no symbolic links here)" -- a count of zero beside a reason for a section that ran. Adding J made that reachable a third way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fdaviddpt
pushed a commit
that referenced
this pull request
Aug 18, 2026
…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>
fdaviddpt
added a commit
that referenced
this pull request
Aug 18, 2026
…ses new ones (#191) (#197) * Cross-file line-number citations rotted silently, and nothing was watching (#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> * Address the audit: an advisory sweep that could report coverage it never 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> * Two prose findings from the re-review (#191) - 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> * README said "Two suites are the exception" and this change made it three (#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> * Rebase onto 5b46095: two citations #194 wrote while this branch was open (#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> * The advisory half said it was blocked on #192, which has landed (#191) 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> --------- Co-authored-by: Florian DAVID <fdavid@digital-village.fr> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
session-start-hook.sh:32parses the hook payload withjit_json_fields+jit_session_keyout ofcommon.sh— the same functionspre-prompt,pre-toolandpre-pathuse — and the comment above it explains why: a second, simpler regex would be a second answer to "what is a session id", and the two would drift. Those three pinLC_ALL=C. This one did not. The same parser in a different locale is not the same parser, so the comment was a claim the code did not carry out.What was measured
3 engines x 2 locales, on a payload whose
session_idcarries a lone0xE9, reading the valuejit_session_key()returns:Cen_US.UTF-8""refused""refused, plus a suppressedtowc: multibyte conversion failure""refused0xE9and all""refused""refusedgawk in a multibyte locale does not match a lone
0xE9against[^A-Za-z0-9_-], so the bare-name check quietly stopped being a bare-name check — on the awk that isawkon most Linux boxes and onubuntu-latest.Bounded honestly. The separators are single-byte and still matched, verified independently:
So nothing ever left the state directory and this was never a traversal. What was lost was agreement: the matching hooks refused that id and kept no marker, while this hook built two marker names out of it and cleared files nothing had written.
The test, and why it is shaped oddly
The obvious fixture — plant the marker the unpinned parse would clear, assert it survives — cannot be built on macOS, because APFS enforces valid UTF-8 in file names. The first draft was red on all three engines for that reason alone, with the redirection error on stderr as the only tell. That is a vacuous negative and would have shipped as coverage.
Section J instead asks a question that has an answer on every filesystem: what was
rmhanded? A shimmedrmfirst on PATH logs its arguments. A refused id never reaches theif [ -n "$SESSION_ID" ]branch, so no marker path is built at all.Red, before the pin:
Green after:
57 passed, 0 failed. Every negative leg is paired with a positive control in the same fixture, engine and locale.The second commit gives section J the third state: this file already had one (section I drives
exit 2, whichrun-all.shkeeps apart from both), and without it a host with no UTF-8 locale printedN passed, 0 failedand exited 0 — byte-identical to a run that drove the gawk cell. Driven all three ways:rc=0normal,rc=2withlocaleshimmed to report US-ASCII,rc=1withJIT_TESTS_REQUIRE_UTF8_LOCALE=1.The
$( )NUL-drop at the same lineMeasured rather than argued about: it costs nothing. one-true-awk truncates the record at the NUL, gawk carries it and the class check matches it, mawk refuses it — no path puts a NUL into the capture on any engine in either locale. Written into the comment so nobody re-measures it.
Scope — the sweep was done, the pins were not
I drove all nine unpinned sites, not just this one. Five of nine diverge. Full table in the report note; the invariant it found is sharper than "every awk that reads untrusted bytes is pinned":
That predicts all nine rows. The four other divergent sites are deliberately not pinned here — they are build tooling under the opposite contract (
tooling.md: fail loudly), and for three of them pinning would convert a loud one-engine abort into a quiet pass-through that relies onrebuild-tsv.sh's bad-byte reporter — which the issue itself documents as broken on mawk/ubuntu-latest. Pinning first would trade a loud bug for a quiet one. Details and the prerequisite are in the report.Two corrections to the issue
jit-dry-run.sh:452at3b8caaa(check_paths_fragment) is absent, and it is one of the five that diverge. The stated count of nine does not match the eight unpinned rows listed.README.md:486isREADME.md:518at HEAD. At HEAD,:486is a sentence about@invocationmacros.The README sentence claimed
LC_ALL=Cwas "pinned on everyawkin the plugin". It was not and still is not. It now claims only what holds and what the sweep verified: everyawkthat reaches the pattern guard —pre-tool-hook.sh,pre-path-hook.shandjit-dry-run.sh's pattern probes. (pre-prompt-hook.shcomposes no$JIT_AWK_GUARD, so it is correctly excluded.)Closes #177.
Verified by the maintainer
Both corrections to the issue are accepted, and the issue was mine. Its per-site table is short by one row and its
README.mdcitation had rotted by 32 lines — which is, with some irony, the exact class of defect #191 is open about, committed inside the issue asking for a locale sweep.Red re-run, mine, in a scratch worktree at this branch with only the pin reverted:
57 passed, 0 failedwith it,56 passed, 1 failedwithout — failing on the gawk leg only,FAIL: [gawk] a malformed session id was treated as a session id, with the awk and mawk legs green in the same run. That one-engine signature is what makes this a locale finding rather than a broken fixture, and it is why the shimmed-rmdesign matters: the obvious fixture cannot exist on APFS, and the draft that tried it was red on all three engines for a reason that had nothing to do with the bug.The refusal to pin the other four is the best decision in this PR and I am accepting it in full. The reasoning is that one-true-awk's abort is today's loudness at those sites; pinning to
Cremoves it and hands the bad byte to a reporter that is silent on mawk — the defaultawkonubuntu-latest. That is trading the loud bug for the quiet one, and refusing it is worth more than the four-line diff that would have looked like completing the issue.Filed rather than carried, with the measurement attached:
rebuild-tsv.shcan write an empty path index and report success::452aborts under a UTF-8 locale, emits 0 rows against 3, and the>> "$tsv"append is unchecked. Plus the:538comment asserting a pin that does not exist, and:353's right-loudness-wrong-reason message. This is the prerequisite, not a follow-up.awkdiverges by locale exactly when it matches a regex against a record that can carry an invalid byte. Sites doing onlyindex(),substr()and==are identical in all six cells — which predicts all nine rows.One class is
not-checkedand is recorded as such rather than rounded to a pass: whether Git Bash onwindows-latesthas a UTF-8 locale, and therefore whether section J runs there or skips. The auditor read the workflow and did not run CI. Section J's third state means a skip is now visible rather than silent, so this PR's own machinery will answer it on the first Windows leg.