fix(github): stop the comment-review gate explaining a pass as an absence (BLO-28988) - #1448
fix(github): stop the comment-review gate explaining a pass as an absence (BLO-28988)#1448allyblockcast[bot] wants to merge 1 commit into
Conversation
…ence (BLO-28988) evaluateCommentReviewGate published state=success alongside the description "No Ally consolidated-review comment attests to reviewing this head." A green status whose own reason asserts that nothing reviewed the head reads as a broken gate, and it cannot be told apart from "reviewed by comment, clean". The states were right; the reasons were not. This context speaks for the COMMENT surface only. Ally reviews most pull requests through a formal pull_request_review object and posts no comment-shaped review, so on those the surface is simply not engaged and there is nothing here to block. Each success reason now states which case applied, and the never-engaged case is distinguished from a superseded earlier-head review. Also fixes a real fail-open: an unevaluable surface (no head SHA) returned success, asserting a clean surface that was never read. It now returns pending. Deliberately NOT changed: a finding still does not carry across a replacement head. That is the reasoned choice from BLO-21907 / #1262 ("exact full-head attestations prevent stale comments from blocking a replacement head"), and requiring a fresh re-review to go green would couple this gate to reviewer delivery, the least reliable link in the chain (BLO-28920, BLO-28968). The SCOPE docblock records which surface this context answers for, so it is not re-widened into a "was this head reviewed at all" gate — that is a branch-protection policy question owned by the formal surface (BLO-26602). Co-Authored-By: Paperclip <noreply@paperclip.ing>
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: 3a29c00
Critical Issues (0)
Important Issues (1)
- [code/gstack-review]
server/src/services/pr-comment-review-gate.ts:157— The "earlier head" branch reports a superseded review for two cases that are not one, and the reason it emits is factually false in both.latestAllyReviewComment(comments, reviewerBotLogin, null)drops the head filter entirely (gate.ts:88), so it matches any Ally consolidated-review comment — including ones whereextractAllyReviewedHeadShareturnednull. That helper returnsnullboth when the body carries zeroReviewed head:lines and when it carries two or more (server/src/services/ally-review-detection.ts:32,attestations.length === 1). Both fall through togate.ts:160: "an earlier head's review does not carry over." There was no earlier head — the attestation was missing or ambiguous.- The multi-attestation case is not hypothetical. Ally's operating instructions forbid quoting a prior
Reviewed head:line precisely because a second match voids the attestation, so the body most likely to land here is a malformed Ally review — exactly the condition an operator reading this status would want surfaced, now described as a routine superseded one. - Worth weighing against the PR's own thesis: the pre-change reason (
"No Ally consolidated-review comment attests to reviewing this head.") was true for this input. It was misleading for case (c) only. The replacement is accurate for (b) and untrue here, so the inaccuracy is narrowed rather than eliminated. - Recommendation: gate the branch on an Ally comment whose extracted head is non-null and differs from
normalizedHead, and give the unattested/ambiguous input its own reason (e.g. "Comment-shaped Ally review present but attests no single head; nothing gates this head."). That keepssuccess— nothing here should block — while the description still says what was actually observed.
Suggestions (3)
- [tests]
server/src/__tests__/pr-comment-review-gate.test.ts:152— the advertised "invariant that nosuccessreason can be read as reporting a deficiency" is implemented as two negative literal-phrase regexes. They pin the old strings, not the property: a future reason such as"Nothing attests to this head"or"No review found for this head"passes both while reintroducing the BLO-28988 shape. Consider asserting eachsuccessreason is a member of an exported allowlist of the three known reasons, so adding or editing one is a conscious act. - [tests] No case exercises a
## Ally — Consolidated PR Reviewcomment with a missing or duplicatedReviewed head:line. That single input is what produces the Important finding above;reviewBody()always emits exactly one attestation, so the gap is invisible from the current helpers. - [comments]
server/src/services/pr-comment-review-gate.ts:132—pendingis unreachable from the only production caller (executeCommentReviewGateCheckreturnsfetch_failedbefore evaluating when no head SHA resolves) and unreachable by construction, since a commit status cannot be posted without a SHA to post it to. The PR body says so; the code does not. A one-line note at this return would save the next reader working it out, and would stop someone "fixing" the caller to publish it.
Strengths
- The
SCOPEdocblock is load-bearing rather than decorative: it names the single question the context answers, pre-refutes the natural "widen it to was this head reviewed at all" change, and cites the concrete failure mode with a sibling precedent (prReviewGateStatusContext). That is the kind of comment that survives contact with a future editor. - Treating an unevaluable surface as
pendingis right on principle even while unreachable — asserting a clean surface you never read is a fail-open, and encoding that now means the invariant holds if a caller ever changes. - Declining to change the stale-head carry-over, and recording the rationale inline on the existing test (
gate.test.ts:62) rather than in the PR description alone, is well-judged restraint; the reasoning about coupling the gate to reviewer delivery is sound. - All five reason strings clear GitHub's 140-char status-description truncation with room to spare (longest ≈ 97 incl. syntax), so the intended message is the one that lands.
- The union widening is safe and was verified end-to-end:
GitHubCommitStatusState(github-app-auth.ts:524) already admits"pending", and the sole consumer (routes/github-webhook.ts:3393) only logs the verdict, so no narrowing or exhaustiveness break is possible.
Recommended Action
- No Critical issues — nothing blocks merge on correctness of the gate's states.
- Address the Important finding this cycle: split the unattested/ambiguous input out of the "earlier head" branch so no
successreason asserts something that did not happen. It is a small change, and it closes the last case where this context still explains itself inaccurately. - Consider the Suggestions opportunistically — the allowlist assertion is the one with lasting value, since it is what keeps the next reason string honest.
|
Closing this unmerged — superseded, and merging it now would regress
Identical fixture, opposite assertion:
My doc comment argued a finding "deliberately does NOT carry across a replacement head." Ally's review of The rest of this PR's substance is already upstream in a better form:
One residual point, not worth a PR and recorded on BLO-28988 rather than lost here: an unresolvable head ( No action wanted on this thread. Tracking continues on BLO-29711 / |
Thinking Path
Linked Issues or Issue Description
Fixes: BLO-28988 (Paperclip)
Refs BLO-21907 / #1262 (the gate this modifies), BLO-26602 (activation), BLO-20673, BLO-28920, BLO-28968
What Changed
evaluateCommentReviewGateno longer emitssuccesswith a reason asserting absence of attestation. Each success now states which case applied.no Ally comment-shaped review gates this head) is distinguished from a superseded earlier-head review (an earlier head's review does not carry over).success, asserting a clean surface that was never read. It now returnspending;CommentReviewGateVerdictgains that state.latestAllyCommentForHeadgeneralized tolatestAllyReviewComment(..., requiredHead), so the superseded case can be detected without a second traversal shape.SCOPEdocblock naming the surface this context speaks for, and why absence of a comment-shaped review is a pass.successreason can be read as reporting a deficiency.Deliberately not changed: a finding still does not carry across a replacement head. That is the reasoned choice from #1262 ("exact full-head attestations prevent stale comments from blocking a replacement head"); requiring a fresh re-review to go green would couple this gate to reviewer delivery, the least reliable link in the chain. The existing test asserting it now carries that rationale inline.
Verification
npx vitest run server/src/__tests__/pr-comment-review-gate.test.ts server/src/__tests__/pr-comment-review-gate-check.test.ts server/src/__tests__/github-webhook.test.ts→ 3 files / 216 tests passed.npx tsc --noEmit -p server/tsconfig.json→ clean.State and description for every surface case, printed from the built function:
Live baseline this reproduces against — PR #1411 head
2ed96898:review/ally-comment=success :: No Ally consolidated-review comment attests to reviewing this head.while Ally had formally reviewed (
COMMENTED, id 290875700) at that exact head. That is case (c): the head was reviewed, on the other surface.Risks
Low. The context is observe-only on this repo (posted, not required in branch protection), so no state change here can block a merge today. No
successcase becomes non-success, so this cannot red-flag a pull request that passes now. The one new state,pending, is reachable only when no head SHA is supplied — unreachable fromrunPrCommentReviewGateCheck, which returnsfetch_failedwithout posting. Reason strings are not parsed anywhere (verified by grep); they are human-facing only.Model Used
Claude Opus 4.5 (
claude-opus-5[1m], 1M context), extended thinking, tool-assisted repository analysis and test execution.Checklist
Blockcast/paperclipgrepped forally-comment|comment-review|review gate|fail-open|28988; only docs: clarify Ally review gate routing #1325 matched and it is docs-only on a different runbookFixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template