Measure how much of pr-selfcheck output survives repetition - #371
Conversation
`/pr-selfcheck` reported a defect in PR #367's body in one run and reported the diff fully accounted for in the next two, against an unedited body. A single run is therefore not evidence, and iterating on the skill's rules by running it once per candidate wording cannot separate a rule that fails to reach a case from a run that happened to miss it. The harness runs the check N times against a merged PR, whose body, diff and head commit are frozen, so every difference between runs comes from the checker. It records each run's report and both transcripts, and reports three tables: the verdict split, per-defect agreement across runs, and what each run had in hand. The third splits a miss into a run that held the evidence and judged differently and one that never gathered it, which is what decides whether a fix belongs in the skill's steps or in its rules. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Each run of the check reads the working tree's state, so a round whose output accumulates as untracked files hands every run a different tree from the one before it. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Round 1 runs the check ten times against the merged #367. Its verdict is PASS in every run, and that agreement carries nothing: no run reported a Fix, and only a Fix moves the verdict. Underneath it the runs agree on almost nothing — nine findings across seven runs group into five defects, no defect is reported by all ten runs, four are reported by exactly one, and the most widely reported reaches five. The defect the three runs in #369 disagreed over is absent from the merged body, which names the rule addition the 13:54 run found unaccounted for. Round 2 therefore targets a fixture carrying #367's diff under the body it held at 13:54, recovered verbatim from those runs' own transcripts. The three transcripts hold byte-identical bodies, so the disagreement recorded in #369 is between runs over one input rather than between two inputs. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Claude Code refuses a session id it has already recorded, so a second round numbering its runs from 1 collided on every run and produced ten empty reports. Each round now carries an offset that keeps its ids disjoint, and round 1's offset is zero so its recorded ids still match what produced it. A non-zero exit was recorded as a completed run. In the tables that is indistinguishable from a run that looked and found nothing, which is how ten collisions passed as ten runs reporting no findings. A failed run is now recorded as failed, excluded from the round, and taken again on restart. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The fixture carries #367's diff under the body that PR held at 13:54 on 2026-08-20, where the diff adds a rule to `Necessary` that the body names nowhere. Two of six runs report it as a Fix; four return PASS. A defect that blocks a PR therefore survives the check two times in three. The runs that reached it are the ones that put the diff's additions and the body's claims side by side -- one read the diff whole against the base file, the other diffed base against head. The four that missed it verified the claims the body makes, recomputing its bullet and file counts correctly, and never went the other way to look for what the body does not mention. Step 6 names no collection to perform before judging, and `Scoped` cannot be decided without one. Reading the gitignored clause-mapping record does not separate them: one run that read it found the defect and one did not, and in round 1 reading it was independent of reporting the defect it backs. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
A run refused its session id exits before the check starts and has no transcript. The id it was refused can already name another round's session, whose transcript was then copied into this round's directory and would be read as this round's data. Four such files sat under round 2 carrying round 1's runs, referenced by no record. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The check fetches the PR head into FETCH_HEAD and reads through it, which is the only reason runs could not overlap. A clone per run removes that shared state, so a round's wall-clock is set by its slowest run rather than by their sum. The clone carries the gitignored paths the check reads and points origin at the real remote, and it is taken from the main tree at the branch that tree has checked out, so a run reaches the same rules through either path. Sample size moves into the round's own entry, alongside the PR and the session offset, so it is fixed before the round starts. Rounds 3 and 4 are declared here for a variant of `Scoped`, each sharing a PR with the round it will be read against. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
`Scoped` asks that every part of the diff sit inside the stated intent without saying what a part is. Across rounds 1 and 2 the check resolved it to the changed-file list in 14 of 16 runs, which any body naming its changed areas satisfies regardless of what those files hold; the two runs that resolved it finer are the two that reached the defect round 2 plants. The unit now names content the diff introduces, separated from moved or reformatted text by a comparison against the base. The preregistration fixes what counts as reaching that defect, the two counts reported, the sample size, and the manipulation check, before either round runs. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Round 3 ran the unit-binding variant against the same defective body round 2 used. Two of six runs reached the omitted rule, the same count round 2 reached under the current wording, and neither raised it to a `Fix`, where round 2 raised both. The comparison does not carry, because the fixture lets a run compare against a `main` that already contains the change under test: #367 is merged, so its added rule is in `main`, and the body's own verification queries cite `main` as their base. Across rounds 2 and 3, five of twelve runs took that comparison and none of them reached the rule, against four of the seven that did not (one-sided Fisher p = 0.07, decided after the data rather than before it). The recall figures from both rounds measure base selection alongside whatever else they measure. Two of the six runs also read the rulebook through `git show origin/main:` rather than the working tree, so the variant never reached them. A rule variant is testable this way only from a base the runs resolve to. What survives is the observation that does not depend on which base a run chose: across both rounds the check resolved `Scoped` to the changed-file list in fourteen of sixteen runs, and the two exceptions are the two runs that reached the omitted rule. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
Closing unmerged. #375 settled that this repository tracks configuration and not the evidence behind it, so a branch whose diff is 82 files of harness code and run transcripts has no destination here. The measurement it records stands and is cited from the tracking issues. The branch stays on the remote, and the harness plus all three rounds are copied to Two things a later round still needs, both still on the remote: the fixture branches Round 2's figure is not a baseline for the next round. Six runs reaching the planted defect twice was measured against the rules as they stood on 2026-08-20, and #372 has since replaced the The |
Purpose
/pr-selfcheckreported a defect in #367's body in one run and reported the diff fully accounted for in the next two, which is what #369 records. Nothing downstream of that observation can proceed on it: a negative from an unstable detector does not separate a rule that fails to reach a case from a run that happened to miss it, so the fourNecessarywordings tested one run apiece during #366 and #367 were never actually tested, and thePASStreated as a gate on both PRs carries whatever a single draw carries.This measures how much of the check's output survives repetition on frozen input, so that any later change to the skill has a number to move.
Key changes
Fix, and only aFixmoves the verdict, so the unanimousPASSrecords the absence of a severity rather than agreement about the PRNecessarythat the body names nowhere, and two of six runs report it while four returnPASSScopedasks whether every part of the diff sits inside the stated intent, which cannot be decided without oneWhat #369 asserted and what the transcripts show
The issue states the body was not edited between its three runs. The body each of those runs fetched is recoverable from its own transcript, and the three are byte-identical at 5596 characters, so the disagreement is between runs over one input rather than between two inputs.
Verification
Fixitems both name the rule the body omits, quoting it from the diff and citing its absence from the base file → recorded inround2/findings.jsonlgit diff --stat b9256e4 FETCH_HEADgives the same three files and170 insertions(+), 137 deletions(-)thatgh pr view 367reportsdiffagainstround2/body-input.mdis empty but for the trailing newlineghaddsgh pr viewresult, compared pairwisejudge splitscolumn is zero throughout, so neither round's grouping rests on a single passNotes
Round 2's fixture carries one difference from the input it reconstructs: the recovered body cites
mainas the base its verification queries ran against, which named the merge base on 2026-08-20 and names a later commit now. Two of six runs reported that divergence. The defect the round measures is decided against the diff and the body alone and is unaffected; the round's other findings are not all free of it.The fixture's branches stay on the remote because round 2 is not reproducible without them.
Follow-up work, none of it in this diff: the runs are sequential only because the check's
git fetch origin pull/<n>/headraces on a shared FETCH_HEAD, so a per-run clone would cut a round from roughly 40 minutes to under 10; and the collection the two successful runs performed is mechanical enough to write into the skill's steps, which is the change this baseline exists to evaluate.