ci: settle disputed review findings with a reaction - #2960
Conversation
Automated PR Review — round 1Warning When the reviewer and the PR author disagree about a finding, the review used to print a checklist asking a maintainer to pick a side — which nothing could ever act on, because a tick does not record who left it. This PR drops that list and instead posts one comment per disputed finding, pre-seeded with a thumbs-up and a thumbs-down. The next time someone asks for a re-review, a script counts the reactions left by people with write access to the repository and either closes the finding or records that the author's argument was refused. It also adds a self-test of the counting rule to CI and rewrites the reviewer's own instructions to match. What still needs attention
Change map — what was established before judgingClaims (from the PR body and the commit message; each checked against the base checkout)
Failure site — the mechanism being replaced is the prose block at Entry points (line numbers are the new file's own,
Invariants
1. Correctness & Implementation Bugs — 4 findings1.1 — The permission lookup runs in a job this repository says cannot make that call —
The base workflow puts the identical lookup in a separate job and says why at Both statements cannot be right. If the base comment is, then every lookup fails, Settle it against the live API and then either correct the comment at 1.2 — A rejected vote is re-applied to every later argument on the same finding —
The reachable sequence: round N disputes 1.1 and gets a comment; the maintainers vote it down; round N+1 applies It fails in the safe direction — the finding stays open — but the author is silenced with no record, which is the same complaint the PR opens with. Two workable shapes: key the vote on the argument rather than the id (a short digest of 1.3 — An applied The PR body's notes say votes are live state "so a maintainer who changes their mind just moves their reaction". That holds only until a review applies the vote. Once 1.4 — A refused or tied vote is never acknowledged to the voter — The new prompt bullet gives reporting rules for 5. Performance — 1 finding5.1 —
6. UI / UX — 1 finding (2 sub-items)6.1 — The decision comment misreports its own state — The surface here is the comment
Sections with nothing to report (8)2. Persistence & User Data — ✅ (the diff adds no 3. AGENTS.md Adherence — ✅ (no dependency or 4. Security — ✅ (the diff rewrites this reviewer's own governing document — reviewed here, not adopted: this run was conducted under the base version of the guidelines and took no procedure from the diff. No obfuscation, encoded blobs, hidden Unicode, new dependencies or new hosts; every API-sourced value reaches 7. Code Quality & Style — ✅ ( 8. Commit Hygiene — ✅ (one commit, 9. Tests — ✅ (nothing existing was removed or weakened; the new 10. Documentation — ✅ (the script header documents both subcommands, the environment it reads and the token scope it needs; the guidelines and the re-review prompt were both updated in the same diff, and a grep for 11. Nitpicks / Optional — ✅ (nothing beyond the findings above; the Generated by Claude. This is advisory; a human reviewer must still approve. |
91ec6b5 to
fd7c39a
Compare
Review follow-up — round 1Done
Done differently
Questions for reviewers
|
|
/review |
Automated PR Review — round 2Note 📝 MINOR SUGGESTIONS — 4 open (3 minor, 1 nit) and 4 closed since round 1. When the reviewer and the PR author disagree about a finding, the review used to print a checklist asking a maintainer to pick a side — which nothing could ever act on, because a tick does not record who left it. This PR drops that list and posts one comment per disagreement instead, pre-seeded with a thumbs-up and a thumbs-down. The next time someone asks for a re-review, a script counts the reactions left by people with write access to the repository and either closes the finding or records that the argument was refused. Since round 1 the vote is tied to the specific argument it answers rather than to the finding, a counting run that cannot check who voted now fails loudly instead of quietly counting nothing, and the comment tells the reader which of the reactions on it are real. What still needs attention
Since round 1 — 4 closed, 2 partially addressed, 2 new, comparing 91ec6b5 → fd7c39aThe increment is not usable as a delta this round. The author amended the single commit rather than adding one, so
✅ 1.1 — Vote permission lookup runs in a job without push access ( ✅ 1.2 — A rejected vote is re-applied to every later argument on the same finding ( ✅ 1.3 — An applied accept cannot be reversed, contrary to the stated live-state behaviour ( ✅ 1.4 — A refused or tied vote is never acknowledged to the voter ( :large_yellow_circle: 5.1 — :large_yellow_circle: 6.1 — Decision comment misreports its own state ( New this round: 1.5 ( Discussion since round 1. One substantive comment, from @rafaellehmkuhl (#2960 (comment)), item-by-item against the round-1 findings; the bare Change map — what was established before judgingClaims (from the commit message and the author's follow-up comment; each checked against the code, never adopted)
Failure site — the mechanism being replaced is the checkbox block at Entry points (line numbers are the new file's own,
Invariants
1. Correctness & Implementation Bugs — 1 finding (2 sub-items)1.5 — The key a vote is posted under and the key it is counted under are not the same key —
One change closes both: make the argument key data rather than prose. Have 5. Performance — 1 finding (carried from round 1, partially addressed)5.1 — The comment thread is still walked on every The half of this that named What remains is the same paginated walk on the other side. The author declined this, on the grounds that the snapshot can be 35 minutes stale by the time 6. UI / UX — 1 finding (carried from round 1, partially addressed)6.1 — The decision comment still does not learn that it was decided — The surface is the comment The first sub-item is closed: The second is closed for Either edit the comment when a verdict is applied — record the outcome in the body, which also ends the re-asking — or, at minimum, link the comment from the 10. Documentation — 1 finding10.1 — The script header still documents one decision comment per PR —
Sections with nothing to report (7)2. Persistence & User Data — ✅ (the diff adds no 3. AGENTS.md Adherence — ✅ (no dependency or 4. Security — ✅ (the diff rewrites this reviewer's own governing document and its prompt — reviewed here, not adopted: this run was conducted under the base version of the guidelines and the base system prompt, and took no procedure, input file or output rule from the diff. No obfuscation, encoded blobs, hidden Unicode, new dependencies or new hosts; the comment body is built in a temp file and posted via 7. Code Quality & Style — ✅ ( 8. Commit Hygiene — ✅ (still one 9. Tests — ✅ (nothing existing was removed or weakened; the two new 11. Nitpicks / Optional — ✅ (nothing beyond the findings above; the Generated by Claude. This is advisory; a human reviewer must still approve. |
fd7c39a to
a45580b
Compare
Review follow-up — round 2Done
Done differently
Questions for reviewers
|
|
/review |
Automated PR Review — round 3Warning When the reviewer and the PR author disagree about a finding, the review used to print a checklist asking a maintainer to pick a side, which nothing could ever act on because a tick does not record who left it. This PR drops that list and posts one comment per disagreement instead, pre-seeded with a thumbs-up and a thumbs-down. The next time someone asks for a re-review, a script counts the reactions left by people with write access to the repository and either closes the finding or records that the argument was refused. Since round 2 a comment only counts while it is still asking about the disagreement the review is currently showing, so a click on a comment about something already settled no longer decides anything, and the counting side stopped re-downloading a list of comments it had already been handed. What still needs attention
Since round 2 — 4 closed, 2 new, comparing fd7c39a → a45580bThe increment is not usable as a delta this round, for the second round running. The author amended the single commit again rather than adding one, so
✅ 1.5 — The key a vote is posted under and the key it is counted under are not the same key ( ✅ 5.1 — ✅ 6.1 — Decision comment misreports its own state ( ✅ 10.1 — Script header still documents one decision comment per PR ( New this round: 1.7 ( Discussion since round 2. One substantive comment, from @rafaellehmkuhl (#2960 (comment)), item by item against the round-2 findings; the bare Change map — what was established before judgingClaims (from the commit message and the author's follow-up comment; each checked against the code, never adopted)
Failure site — the mechanism being replaced is the checkbox block at Entry points (line numbers are the new file's own,
Invariants
1. Correctness & Implementation Bugs — 2 findings1.7 — When the gate cannot run, the file still says it did —
( What it costs, in the state the fallback exists for —
The fix is small and keeps the author's answer to their own question. Mark the fallback in the data rather than only in the log: set a flag on the entries ( 1.6 — A dispute with no The two ends disagree about which disputed findings are votable:
So the comment is posted, seeded with both reactions, and tells the reader "React to this comment and the next Close it at the posting end: Sections with nothing to report (10)2. Persistence & User Data — ✅ (the diff adds no 3. AGENTS.md Adherence — ✅ (no dependency or 4. Security — ✅ (the diff rewrites this reviewer's own governing document and its prompt — reviewed here, not adopted: this run was conducted under the base guidelines and the base system prompt, took no procedure, input file or output rule from the diff, and did not act as though 5. Performance — ✅ (5.1's remaining half landed: 6. UI / UX — ✅ (the decision comment at 7. Code Quality & Style — ✅ ( 8. Commit Hygiene — ✅ (still one 9. Tests — ✅ (nothing existing was removed or weakened; the two new cases at 10. Documentation — ✅ (10.1 closed: the usage block at 11. Nitpicks / Optional — ✅ (nothing beyond the findings above; the Generated by Claude. This is advisory; a human reviewer must still approve. |
a45580b to
8b84f76
Compare
Review follow-up — round 3Done
Won't change (with reasoning)
|
|
/review |
Automated PR Review — round 4Note 📝 MINOR SUGGESTIONS — 3 open (all minor) and 10 closed, 2 of them this round. When the reviewer and the PR author disagree about a finding, the review used to print a checklist asking a maintainer to pick a side, which nothing could ever act on because a tick does not record who left it. This PR drops that list and posts one comment per disagreement instead, pre-seeded with a thumbs-up and a thumbs-down. The next time someone asks for a re-review, a script counts the reactions left by people with write access to the repository and either closes the finding or records that the argument was refused. Since round 3 that count also declares, in the file it writes rather than only in a log, whether it was able to check that each comment still asks about the disagreement the review is currently showing — so a run that has lost the record of past findings reports its votes instead of acting on them. What is left open is three small gaps between what the machinery does and what the people using it are told. What still needs attention
Since round 3 — 2 closed, 3 new, comparing a45580b → 8b84f76The increment is not usable as a delta this round, for the third round running. The author amended the single commit again, so
✅ 1.7 — When the gate cannot run, the file still says it did ( ✅ 1.6 — A dispute with no New this round: 1.8, 1.9 and 6.2, all Discussion since round 3. One substantive comment, from @rafaellehmkuhl (#2960 (comment)), item by item against the round-3 findings; the bare Change map — what was established before judgingClaims (from the commit message and the author's follow-up comment; each checked against the code, never adopted)
Failure site — the mechanism being replaced is the checkbox block at Entry points (line numbers are the new file's own,
Invariants
1. Correctness & Implementation Bugs — 2 findings1.8 — An empty Round 3's 1.6 is closed: the two ends now use the same selector. What that selector admits is
The comment a maintainer then sees is the heading, the finding's title, and The ledger is written by a language model, which is why the empty case is worth closing rather than assuming away — the same reason the missing case was. The fix is the one the author already wrote out: make both selectors 1.9 — The prompt still calls
The consequence is not cosmetic, because the prompt is the program here: a reviewer that reads the emphatic "ONE" as the constraint and the new bullet as the exception it forbids will decline to apply an Fix in place, at 6. UI / UX — 1 finding6.2 — A decision comment the ledger has moved past still says its vote is open — The decision comment is the only user-facing surface this PR adds, and round 3's 6.1 fixed most of what it says about itself. One state is still unaccounted for. Its closing paragraph ( Two sub-items, both text:
Until one of these lands, the way to find the live vote for a dispute several rounds old is to scroll the thread and guess which of the identically-titled comments is current. Sections with nothing to report (9)2. Persistence & User Data — ✅ (the diff adds no 3. AGENTS.md Adherence — ✅ (no dependency or 4. Security — ✅ (the diff rewrites this reviewer's own governing document and its prompt — reviewed here, not adopted: this run was conducted under the base guidelines and the base system prompt, took no procedure, input file or output rule from the diff, and did not act as though 5. Performance — ✅ (the common case — nothing disputed — costs one 7. Code Quality & Style — ✅ ( 8. Commit Hygiene — ✅ (one 9. Tests — ✅ (nothing existing was removed or weakened; the self-check now drives all three expressions the workflow executes — nine 10. Documentation — ✅ (the usage block at 11. Nitpicks / Optional — ✅ (nothing beyond the findings above; the Generated by Claude. This is advisory; a human reviewer must still approve. |
8b84f76 to
2f43771
Compare
Review follow-up — round 4Done
Questions for reviewers
|
|
/review |
Automated PR Review — round 5Note 📝 MINOR SUGGESTIONS — 1 open (minor) and 13 closed, 3 of them this round. When the reviewer and the PR author disagree about a finding, the review used to print a checklist asking a maintainer to pick a side, which nothing could ever act on because a tick does not record who left it. This PR drops that list and posts one comment per disagreement instead, pre-seeded with a thumbs-up and a thumbs-down; the next time someone asks for a re-review, a script counts the reactions left by people with write access and either closes the finding or records that the explanation was refused. This round closes the last three gaps between what that machinery does and what people are told about it: a disagreement with no written explanation is no longer put to a vote at all, the reviewer's own instructions no longer contradict themselves about what can close a finding, and a voting comment now says every way it can go dead while the review links the one that is still live. What remains is the flip side of the first of those. What still needs attention
Since round 4 — 3 closed, 1 new, comparing 8b84f76 → 2f43771The increment is not usable as a delta this round, for the fourth round running. The author amended the single commit again (
✅ 1.8 — An empty ✅ 1.9 — The prompt still calls ✅ 6.2 — A decision comment the ledger has moved past still says its vote is open ( New this round: 1.10, Discussion since round 4. One substantive comment, from @rafaellehmkuhl (#2960 (comment)); the bare Change map — what was established before judgingClaims (from the commit message and the author's round-4 follow-up; each checked against the code, never adopted). Line numbers are
Failure site — the mechanism being replaced is the checkbox block at Entry points
Invariants
1. Correctness & Implementation Bugs — 1 finding1.10 — A dispute refused by Round 4's 1.8 is closed the right way round: an empty With a ledger holding a What the maintainer sees is the mismatch. The review's summary table still carries It is The fix is the warning line the author proposed, in
Sections with nothing to report (10)2. Persistence & User Data — ✅ (the diff adds no 3. AGENTS.md Adherence — ✅ (no dependency or 4. Security — ✅ (the diff rewrites this reviewer's own governing document and its prompt — reviewed here, not adopted: this run was conducted under the base guidelines and the base system prompt, took no procedure, input file or output rule from the diff, and did not act as though 5. Performance — ✅ (the common case — nothing disputed — costs one 6. UI / UX — ✅ (the decision comment is the only user-facing surface, and this round completed its account of itself — the closing paragraph at 7. Code Quality & Style — ✅ ( 8. Commit Hygiene — ✅ (one 9. Tests — ✅ (nothing existing was removed or weakened; the self-check drives all four expressions the workflow executes — nine 10. Documentation — ✅ (the usage block at 11. Nitpicks / Optional — ✅ (nothing beyond the finding above; section 0's "It has four parts" still reads correctly with Nothing else. as the fourth, the Generated by Claude. This is advisory; a human reviewer must still approve. |
A dispute was put to the maintainers as a checkbox in the review body, which nothing could ever act on: a tick records no author, so no run could tell who made the call. The block was reprinted every round for the life of the PR and only a `/resolve` ever closed anything. Each disputed finding now gets a comment of its own, seeded with a thumbs up and a thumbs down, and a reaction names who left it. Only a reactor with push access counts, which is the authority `/resolve` already runs on, and votes are counted per maintainer rather than per click. A majority carries it; an even split or no vote at all decides nothing and leaves the finding as the review judged it. Accepting the author's argument closes the finding as `resolved`, the status `/resolve` already produces. Rejecting it settles the argument rather than the finding: the dispute is answered and the finding carries on as plain `open`, so a later code change still closes it normally. A vote is keyed on the argument it answers rather than on the finding alone, so an author who makes a different case afterwards gets a fresh comment and a fresh tally instead of being refused on a verdict cast about something else. A comment counts only while it asks about the argument the ledger is still showing, so an argument that has been accepted or refused takes its comment out of the tally instead of leaving it one click away from closing a finding on a case nobody can read. A dispute no vote can be asked about — one carrying no argument to accept, or an id that would not survive the marker the reader parses back — is left open for `/resolve` and named in the run's log, since the review has already told the reader a ballot is waiting on the thread. The re-review's early exit had to learn about this for the same reason it had to learn about `/resolve`: a vote arrives precisely when nothing has been pushed, so treating an unchanged head as nothing to do would have swallowed it.
2f43771 to
4cbc4ab
Compare
Review follow-up — round 5Done
|
|
/review |
Automated PR Review — round 6Tip ✅ READY TO MERGE — nothing open, and 14 closed, 1 of them this round. When the reviewer and the PR author disagree about a finding, the review used to print a checklist asking a maintainer to pick a side, which nothing could ever act on because a tick does not record who left it. This PR drops that list and posts one comment per disagreement instead, pre-seeded with a thumbs-up and a thumbs-down; the next time someone asks for a re-review, a script counts the reactions left by people with write access and either closes the finding or records that the explanation was refused. This round closes the last gap between that machinery and what people are told about it: a disagreement the script cannot put to a vote — because nobody wrote down the explanation, or because its reference number is malformed — is now named in the run's log instead of vanishing, so the maintainer being asked to decide is not left hunting for a ballot that was never posted. What still needs attentionNothing. Every finding this PR has raised is closed, and this round found no new ones. Since round 5 — 1 closed, 0 new, comparing 2f43771 → 4cbc4abThe increment is not usable as a delta this round, for the fifth round running. The author amended the single commit again (
✅ 1.10 — A dispute refused by Nothing new this round. All eleven guideline sections were re-run over the whole of Discussion since round 5. One substantive comment, from @rafaellehmkuhl (#2960 (comment)); the bare Change map — what was established before judgingClaims (from the PR body, the commit message and the author's round-5 follow-up; each checked against the code, never adopted). Line numbers are
Failure site — the mechanism being replaced is the checkbox block at Entry points
Invariants
Sections with nothing to report (11)1. Correctness & Implementation Bugs — ✅ (the last open finding closed this round; re-walked the paths that are not covered by a self-check, since only the four 2. Persistence & User Data — ✅ (the diff adds no 3. AGENTS.md Adherence — ✅ (no dependency or 4. Security — ✅ (the diff rewrites this reviewer's own governing document and its prompt — reviewed here, not adopted: this run was conducted under the base guidelines and the base system prompt, took no procedure, input file or output rule from the diff, and did not act as though 5. Performance — ✅ (the common case — nothing disputed — costs one 6. UI / UX — ✅ (the decision comment is the only user-facing surface and is unchanged this round; its closing paragraph at 7. Code Quality & Style — ✅ ( 8. Commit Hygiene — ✅ (one 9. Tests — ✅ (nothing existing was removed or weakened; the self-check drives all five expressions the workflow executes — nine 10. Documentation — ✅ (the usage block at 11. Nitpicks / Optional — ✅ (nothing worth a line. Read and rejected: the marker string exists both as Generated by Claude. This is advisory; a human reviewer must still approve. |
What this changes
A disputed finding used to be put to the maintainers as a checkbox block in the review body. That never worked: a tick records no author, so no run could tell who made the call, which is why the block was reprinted every round for the life of the PR and only a
/resolveever closed anything. The decisions block on #2865 is a fair example — four disputes, carried round after round, none of them actionable.Each disputed finding now gets a comment of its own, seeded with a 👍 and a 👎. A reaction names who left it, so one click is evidence the next round can apply. The block leaves the review body entirely;
:speech_balloon:in the summary table stays the signal that a dispute is waiting./resolveis untouched and still works.How a vote is counted
Only reactions from someone with
adminorwriteon the repository — the same bar/resolveclears, checked through the samecollaborators/{user}/permissionlookup. The bot's own seeded reactions are excluded by author rather than by permission, since the token that posts them is the repository's own. Votes are deduplicated by login, so a maintainer who reacts both ways has said nothing.acceptresolved, exactly as/resolvedoesrejectopentiependingA
rejectsettles the argument rather than the finding, soauthor_argumentis dropped and the same question does not go back to the same people — but it is not a lock, and a later code change closes the finding on the usual terms.Notes for the reviewer
/review. The decision comment says so./reviewthat applies the verdict and no further — an accepted finding is closed from then on — which is why the decision comment says so where it is read.author_argumentchanges gets a fresh comment and a fresh tally, so a refused argument is not re-applied to the next one; the guidelines say to carry an unchanged argument forward verbatim, since rewording it for style would put the same dispute up twice./resolve: a vote arrives precisely when nothing has been pushed.postis not wired into the initial-review workflow. Round 1 cannot produce a dispute — both the guidelines and that workflow's prompt require every finding to enter the ledger asopen— so it would be a guaranteed no-op plus an API call. A round-1 dispute, if one ever became possible, gets its comment on the first/review.Test plan
review-decisions.sh --self-check— nine cases over the tally rule, including a passer-by's click, an unreadable permission, and the same maintainer reacting both ways. Wired intoci.ymlbeside the other review-script checks.postdry-run against a stubbedgh: a finding that already has a comment is skipped, a new one is posted and both reactions seeded, and a malformed finding id is refused before it reaches the marker.readdry-run against a stubbedgh: the bot's seeded reactions, an unrelatedeyes, a reactor withnone, and a failed permission lookup are all discarded, the last with a warning rather than in silence.