fix(ally): make the same-SHA idempotency check actually fire - #1599
Merged
Conversation
The check filtered reviews by `ally-paperclip[bot]`, a login that has never posted a review in this repo. Across the four PRs currently violating the consistency guard there are 16 Ally reviews, all `allyblockcast[bot]` — the login the guard itself treats as canonical (ALLY_APP_REVIEWER_LOGIN). So LAST_REVIEW_SHA was always empty, never equalled HEAD_SHA, and the skip never fired: every wake re-reviewed. That is the mechanism behind `one-verdict-per-head`, which has failed 98 of its last 100 hourly runs since 2026-08-28. Verified against live data — the old filter yields "" on #1304/#1525/#1360 (skip: NO); the new one finds 2 operative reviews at head on each (skip: YES), so it would have prevented all three. Three further defects in the same block: - `| last` answered "what did I review most recently" rather than "have I reviewed THIS head", which is wrong whenever a head is revisited. Now counts reviews at HEAD_SHA. DISMISSED excluded, matching the guard's notion of operative. - "Wake on github_pr_review always re-reviews regardless of SHA" explicitly defeated the check. Invariant I1 allows at most one operative App review per head, so a same-head re-review violates by construction regardless of trigger — and a COMMENTED review cannot be dismissed via the API, so the violation is permanent until the head moves. Removed; a genuine re-review needs a new commit. - The skip path said "leave a comment" while the documented command is `gh pr review --comment`, which files a PullRequestReview and recreates the violation. Pinned to `gh pr comment`. Controls: real head -> 2, bogus SHA -> 0, non-head commit -> 0. Does not clean up the four existing violations; those heads are frozen and their COMMENTED reviews are undismissable. This stops new ones.
|
🔗 Paperclip issue: BLO-19778 |
1 similar comment
|
🔗 Paperclip issue: BLO-19778 |
|
Hey @kkroo! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 11d6bb3
Critical Issues (1)
- [native-codex]
.planning/ally-agent/AGENTS.md:24-27— The idempotency check treats anyallyblockcastUser-seat review as equivalent to the required Ally App review, without checkinguser.type, immutable user ID, or the canonical consolidated body/attestation. A same-head User approval can therefore makeALREADY > 0and cause Ally to skip the App's required formal review, leavingreview/ally-completeunsatisfied (and on an App-authored PR, skipping the required formal AppCOMMENTEDreview entirely).- Restrict this check to the trusted App identity (
user.type == Bot, canonical App login and ID) and require the consolidated header plus exactly one matchingReviewed head:attestation. The User seat must not suppress the App lane.
- Restrict this check to the trusted App identity (
Important Issues (1)
- [native-codex]
.planning/ally-agent/AGENTS.md:24-28— With--paginate,gh api --jq '[...] | length'evaluates once per response page and emits multiple newline-separated counts. Once the reviews endpoint has more than one page,ALREADYcan be1\n0, and[ "${ALREADY:-0}" -gt 0 ]reports an integer-expression error and falls through to another review instead of skipping.- Aggregate pagination before counting, or use a jq expression that emits a single scalar after all pages are collected, then validate the result before the numeric comparison.
Suggestions (0)
Strengths
- The change correctly replaces the last-review lookup with a current-head query and excludes dismissed reviews.
- The skip note correctly uses
gh pr commentrather than creating another formal review object.
Recommended Action
- Fix the App/User identity distinction and pagination aggregation before merge.
- Add focused shell-level coverage for a User-only same-head review and multi-page review responses.
…logins The Step 2 check depends on a constant that lives in code (the canonical Ally App reviewer login) but is restated in prose, with nothing linking the two. That is how it came to reference a login that has never posted a review here: a stale identifier in an instruction file fails silently and is indistinguishable from working code. Adds a contract test importing ALLY_APP_REVIEWER_LOGIN / ALLY_USER_REVIEWER_LOGIN from check-ally-review-consistency.mjs and asserting the Step 2 block matches them, carries no wake-reason exemption, and posts its skip note as an issue comment. Each assertion carries a positive control so an empty or mis-scoped extraction cannot pass vacuously. Verified by mutation: reintroducing each defect independently fails exactly one test; the fixed file passes 3/3. Also reworded two sentences in Step 2 that quoted the anti-patterns verbatim — the first run of this test failed on its own documentation. The command assertion is now scoped to the bash fence so the prose stays free to name what it is warning against.
|
Hey @kkroo! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: bdf2d16
Prior Findings Dispositioned (2)
- prior:11d6bb3 critical 1 — still-present —
.planning/ally-agent/AGENTS.md:89-92— The filter still matches by login alone, including theallyblockcastUser seat, without requiring Bot type, immutable App ID, canonical consolidated body, or exact attestation. A User review can still suppress the required App review. - prior:11d6bb3 important 1 — still-present —
.planning/ally-agent/AGENTS.md:89-93— The paginatedgh api --paginate --jq "[...] | length"command still emits one count per response page; a multi-page response can makeALREADYnewline-separated and cause the numeric comparison to fail rather than skip.
Critical Issues (1)
- [native-codex]
.planning/ally-agent/AGENTS.md:89-92— The idempotency check treats any matchingallyblockcastlogin as sufficient, including the User seat. A same-head User review can makeALREADY > 0, causing Ally to skip the required trusted App review and leaving the review gate unsatisfied.- Restrict the predicate to the trusted App identity (
user.type == Bot, canonical login and immutable App ID), and require the canonical consolidated header plus exactly one matchingReviewed head:attestation. The User seat must not suppress the App lane.
- Restrict the predicate to the trusted App identity (
Important Issues (1)
- [native-codex]
.planning/ally-agent/AGENTS.md:89-93— With--paginate, the--jqfilter is evaluated independently for each response page. More than one page can produce output such as1\n0;[ "${ALREADY:-0}" -gt 0 ]then raises an integer-expression error and falls through to another review instead of reliably skipping.- Aggregate all pages before counting, or use a pagination-safe command that emits one validated scalar.
Suggestions (0)
Strengths
- The check now evaluates reviews at the current head instead of using only the last review.
- Dismissed reviews are excluded from the operative count.
- The skip note correctly uses
gh pr commentrather than creating another formal review object.
Recommended Action
- Fix the App/User identity distinction before merge.
- Make the paginated count produce one scalar and add focused tests for User-only reviews and multi-page responses.
13 tasks
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.
Thinking Path
one-verdict-per-head(ally-review-consistency.yml, hourly) has failed 98 of its last 100 runs, continuously since 2026-08-28. It is not a required check — master requires onlyverify— so it has been red without blocking anything.The first question was whether the guard is over-strict or the data is real. It is real. On #1525 both flagged reviews carry
commit_id=05325ee7, which is still the current head, submitted three days apart — so this is not force-push re-anchoring, and the guard'soperativefilter (Ally identity, not dismissed,commit_id == head) is counting exactly what it should.The four violations then turned out to have four different shapes, which ruled out a single-cause story: #1304 is the same body twice (identical md5) one minute apart; #1360 is two full canonical reviews 2h39m apart; #1525 is a canonical review plus a 176-char status note filed as a review; #1316 is a
## CTO reviewposted under the Ally App identity.The violation set is also churning, not frozen — #1114/#1530 dropped out and #1304/#1525 joined since 2026-08-28 — so the producer is still emitting duplicates and cleanup alone would be a treadmill.
What Changed
.planning/ally-agent/AGENTS.mdStep 2 only. Four defects in one block:ally-paperclip[bot], which has never posted a review here. All 16 Ally reviews across the four PRs areallyblockcast[bot]— the login the guard itself declares canonical.LAST_REVIEW_SHAwas therefore always empty and the skip never fired.| lastanswered "what did I review most recently" instead of "have I reviewed THIS head". Now counts operative reviews atHEAD_SHA, excluding DISMISSED to match the guard.Wake on github_pr_review always re-reviews regardless of SHAexplicitly defeated the check. I1 permits at most one operative App review per head, so a same-head re-review violates by construction whatever the trigger.gh pr review --commentfor the skip note files aPullRequestReview, recreating the violation. Pinned togh pr comment.Verification
Run against live GitHub data, old filter vs new:
''''''So the corrected check would have prevented all three duplicates.
Discriminating controls on #1304: real head →
2; bogus SHAdeadbeef…→0; a non-head commit (master) →0. My first attempt at a negative control used #1597, which also returns 2 — it has two Ally reviews at head as well and is merged, so the guard skips it. That was not a control and was replaced.Tests
scripts/ally-agent-idempotency-contract.test.mjsimportsALLY_APP_REVIEWER_LOGIN/ALLY_USER_REVIEWER_LOGINfrom the guard and pins the instruction against them, so this identifier drift fails a test instead of silently disabling the check. Also asserts no wake-reason exemption and that the skip note goes throughgh pr comment.Every assertion carries a positive control, so a mis-scoped or empty extraction cannot pass vacuously. Verified by mutation — each defect reintroduced on its own fails exactly one test; the fixed file passes 3/3.
Worth noting the test caught a defect in my own first draft: the prose quoted the anti-patterns verbatim, so an absence check over the whole section fired on the documentation. The command assertion is now scoped to the bash fence.
Risks
Instruction-file change, so no unit test covers it; verification is empirical against live data.
Two behaviour changes worth naming. A
github_pr_reviewwake on an unchanged head now posts a plain comment and exits instead of re-reviewing — intended, since the second review was the violation, but it does mean a re-review genuinely wanted at the same head now requires a new commit. And the skip note becomes an issue comment rather than a review object, so anything keying off a review record for that note will no longer see one.This does not clear the four existing violations. Those heads are frozen and
COMMENTEDreviews cannot be dismissed through the API, so they persist until each head moves or the PR closes. Expect the guard to stay red until then; this stops the set from growing.Model Used
claude-opus-5[1m]
Refs BLO-19778 (cited in the guard's own violation message).
.planning/ally-agent/AGENTS.mdor this idempotency check.