Skip to content

fix(github): require a substantive review at head; classify vacuous bot checks (factory#432) - #451

Merged
khaliqgant merged 2 commits into
mainfrom
lane/factory-432-merge-gate-review-at-head
Sep 3, 2026
Merged

fix(github): require a substantive review at head; classify vacuous bot checks (factory#432)#451
khaliqgant merged 2 commits into
mainfrom
lane/factory-432-merge-gate-review-at-head

Conversation

@khaliqgant

@khaliqgantkhaliqgant commented Sep 3, 2026

Copy link
Copy Markdown
Member

Summary

Scoped to parts (a) and (b) of #432 only.

  • (a) Review-at-head predicate: evaluateGithubMergeGate now requires at
    least one review anchored to headRefOid, from someone other than the PR
    author, carrying a non-empty body or inline comments — not just a
    repo-level reviewDecision === 'APPROVED', which survives stale commits
    and empty-bodied approvals (relay#1638: 14 reviews, exactly one at head,
    empty body). Refusal reasons name which condition failed: no review at
    head, review at head is author-only, or review at head has no content.

  • (b) Vacuous check classification: statusCheckRollup entries are no
    longer collapsed to a bare state string. Each entry keeps its
    context/description and is classified REAL, VACUOUS (matching the
    description-string markers from merge gate returns READY on PRs nobody reviewed: reviewDecision is not bound to head, and a vacuous bot check is indistinguishable from a real one #432 — Devin trial-expired, CodeRabbit
    rate-limited/OSS-skip, cubic seat-unassigned/line-limit, codex usage
    limits), or BLOCKING. A check set that is all-vacuous is refused the
    same as having no checks at all, instead of satisfying the "successful
    status checks observed" guard. GithubMergeGateVerdict.live gains
    checkSignals: CheckSignal[]; checkStates: string[] is retained,
    derived, for one release.

Out of scope (per the issue and lane instructions): part (c), the new
review-at-head.yml CI workflow, and the adjacent cubic seat / CodeRabbit
OSS-trigger / Devin trial repairs — those are separate ops work.

Tests

  • Reproduces relay#1638's shape: several substantive-but-stale reviews plus
    one empty-bodied approval at head → refused, not READY.
  • No review anchored to head at all → refused, naming the newest stale
    commit.
  • Only review at head is by the PR author → refused.
  • A review at head with no body but inline comments → accepted as
    substantive.
  • Devin Full review skipped: trial expired..., CodeRabbit Review rate limited / OSS-skip, cubic seat-unassigned / line-limit → each classified
    VACUOUS, not REAL; an all-vacuous check set refuses.
  • A genuine SUCCESS with an ordinary description classified REAL.
  • A real check alongside a vacuous one is still READY, with both counts
    surfaced in the reason.
  • Extended MountedGithubMergeGate coverage for the two new required
    mounted fields (reviews, author) failing closed as a capability error
    when absent.

Test plan

  • npx vitest run src/github/merge-gate.test.ts src/github/gh-identity.test.ts — 34 passed
  • npx vitest run src/github src/hosted src/writeback — 197 passed
  • npx tsc -p tsconfig.build.json --noEmit — clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01S2QvRkHKvzpduLuDFRipaF


Summary by cubic

Fixes parts (a) and (b) of factory#432 by requiring a substantive third-party review anchored to the current head instead of relying on repo-level APPROVED alone. Status checks now distinguish real passes from vacuous bot skips, so an all-vacuous check set no longer makes a PR ready.

  • Reviews need a non-empty body or inline comments; stale, author-only, and empty head reviews are refused with specific reasons.
  • Check signals retain their context and description and classify as REAL, VACUOUS, or BLOCKING; derived checkStates remains available for one release.
  • Mounted PR metadata now requires reviews and author and fails closed when either is missing.

Written for commit ca472c3. Summary will update on new commits.

Review in cubic

…ify vacuous bot checks (factory#432)
`evaluateGithubMergeGate` treated `reviewDecision === 'APPROVED'` and a
non-blocking `statusCheckRollup` as evidence of review, but neither is
bound to the commit that actually merges nor to content. relay#1638
merged on a repo-level APPROVED whose only review at head was an empty
body; a Devin/CodeRabbit/cubic check reporting a skipped or rate-limited
review reads identically to a real pass.
Adds a review-at-head predicate requiring at least one review pinned to
`headRefOid`, from someone other than the PR author, carrying a body or
inline comments — refusing with a reason naming which condition failed
(no review at head, author-only, or vacuous). Widens `statusCheckRollup`
parsing to keep each entry's description and classify it REAL, VACUOUS
(matching known trial-expired/rate-limited/seat-unassigned/line-limit
markers), or BLOCKING, so an all-vacuous check set is refused the same
as no checks at all instead of satisfying the "checks observed" guard.
Out of scope for this change: the CI check for the same predicate, and
the seat/config repairs (cubic seats, CodeRabbit OSS trigger, Devin
trial) — tracked separately in factory#432.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2QvRkHKvzpduLuDFRipaF
Session-Id: 56185aca-7dc6-42c8-b69e-650394d744aa
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 3, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-09-03T12:47:28.440246Zf9cc3dfPR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitaiBot commented Sep 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 17 seconds.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: a6655491-afb0-4366-bffe-8097c992b282

📥 Commits

Reviewing files that changed from the base of the PR and between 29d52c1 and ca472c3.

📒 Files selected for processing (4)
  • src/github/gh-identity.test.ts
  • src/github/merge-gate.test.ts
  • src/github/merge-gate.ts
  • src/orchestrator/factory.test.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai review

Requested for exact head f9cc3dff580de97deab3155797fc1d5f68ed939a.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f9cc3dff58

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +315 to +316
['reviews', Array.isArray(reviews) ? reviews : undefined],
['author', author],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update existing mounted-gate fixtures for required review fields

Requiring reviews and author here makes the existing MUST FIRE: the default gate reads readiness from the mount test in src/orchestrator/factory.test.ts reject with a capability error, because its otherwise-ready mounted payload still contains neither field while the test expects READY. The normal full vitest run therefore fails even though the two targeted test files were updated; update that fixture (and any equivalent mounted payloads) with a third-party substantive review at the expected head.

Useful? React with 👍 / 👎.

login,
state: state.toUpperCase(),
commitId,
bodyLength: (stringValue(record.body) ?? '').length,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject whitespace-only review bodies

When the only third-party review at the current head has a body such as " \n" and no inline comments, bodyLength > 0 treats it as substantive and the gate can return READY using a stale repo-level approval plus an ordinary successful check. This defeats the newly introduced substantive-review guard for bot/API reviews whose nominally non-empty bodies contain only whitespace; compute the body length after trimming or otherwise test for meaningful content.

Useful? React with 👍 / 👎.

@cubic-dev-aicubic-dev-aiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/github/merge-gate.ts">
<violation number="1" location="src/github/merge-gate.ts:315">
P2: Update the existing mounted merge-gate fixtures to include `reviews` and `author`; otherwise `MountedGithubMergeGate` now throws a capability error and the default-gate test cannot reach its expected `READY` result.</violation>
<violation number="2" location="src/github/merge-gate.ts:379">
P1: When a blocking check description contains a vacuity marker, this branch labels it `VACUOUS` before inspecting its state. `evaluateGithubMergeGate` can then proceed with a failed check; apply these markers only to non-blocking states.</violation>
<violation number="3" location="src/github/merge-gate.ts:382">
P2: A successful rollup with lowercase `state` or `status` is classified as `BLOCKING` because `classifyCheckKind` compares the raw value with uppercase states. Normalize the state before the set lookup.</violation>
<violation number="4" location="src/github/merge-gate.ts:435">
P2: Whitespace-only review bodies satisfy the substantive-review predicate because the code checks length without trimming. Trim the body before calculating `bodyLength`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

stringValue(record.text)

const classifyCheckKind = (state: string, description: string | undefined): CheckSignal['kind'] => {
if (description && VACUOUS_REVIEW_MARKERS.some((marker) => marker.test(description))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When a blocking check description contains a vacuity marker, this branch labels it VACUOUS before inspecting its state. evaluateGithubMergeGate can then proceed with a failed check; apply these markers only to non-blocking states.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/github/merge-gate.ts, line 379:
<comment>When a blocking check description contains a vacuity marker, this branch labels it `VACUOUS` before inspecting its state. `evaluateGithubMergeGate` can then proceed with a failed check; apply these markers only to non-blocking states.</comment>
<file context>
@@ -311,31 +349,127 @@ const refuse = (reason: string, live: GithubMergeGateVerdict['live']): GithubMer
+ stringValue(record.text)
+
+const classifyCheckKind = (state: string, description: string | undefined): CheckSignal['kind'] => {
+ if (description && VACUOUS_REVIEW_MARKERS.some((marker) => marker.test(description))) {
+ return 'VACUOUS'
+ }
</file context>
Suggested change
if(description&&VACUOUS_REVIEW_MARKERS.some((marker)=>marker.test(description))){
if(nonBlockingCheckStates.has(state)&&description&&VACUOUS_REVIEW_MARKERS.some((marker)=>marker.test(description))){

login,
state: state.toUpperCase(),
commitId,
bodyLength: (stringValue(record.body) ?? '').length,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Whitespace-only review bodies satisfy the substantive-review predicate because the code checks length without trimming. Trim the body before calculating bodyLength.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/github/merge-gate.ts, line 435:
<comment>Whitespace-only review bodies satisfy the substantive-review predicate because the code checks length without trimming. Trim the body before calculating `bodyLength`.</comment>
<file context>
@@ -311,31 +349,127 @@ const refuse = (reason: string, live: GithubMergeGateVerdict['live']): GithubMer
+ login,
+ state: state.toUpperCase(),
+ commitId,
+ bodyLength: (stringValue(record.body) ?? '').length,
+ inlineCommentsAtHead: reviewInlineComments(record),
+ }]
</file context>
Suggested change
bodyLength: (stringValue(record.body)??'').length,
bodyLength: (stringValue(record.body)??'').trim().length,

if (description && VACUOUS_REVIEW_MARKERS.some((marker) => marker.test(description))) {
return 'VACUOUS'
}
return nonBlockingCheckStates.has(state) ? 'REAL' : 'BLOCKING'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: A successful rollup with lowercase state or status is classified as BLOCKING because classifyCheckKind compares the raw value with uppercase states. Normalize the state before the set lookup.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/github/merge-gate.ts, line 382:
<comment>A successful rollup with lowercase `state` or `status` is classified as `BLOCKING` because `classifyCheckKind` compares the raw value with uppercase states. Normalize the state before the set lookup.</comment>
<file context>
@@ -311,31 +349,127 @@ const refuse = (reason: string, live: GithubMergeGateVerdict['live']): GithubMer
+ if (description && VACUOUS_REVIEW_MARKERS.some((marker) => marker.test(description))) {
+ return 'VACUOUS'
+ }
+ return nonBlockingCheckStates.has(state) ? 'REAL' : 'BLOCKING'
+}
+
</file context>
Suggested change
returnnonBlockingCheckStates.has(state) ? 'REAL' : 'BLOCKING'
returnnonBlockingCheckStates.has(state.trim().toUpperCase()) ? 'REAL' : 'BLOCKING'

['headRefOid', headRefOid],
['reviewDecision', reviewDecision],
['statusCheckRollup', Array.isArray(statusCheckRollup) ? statusCheckRollup : undefined],
['reviews', Array.isArray(reviews) ? reviews : undefined],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Update the existing mounted merge-gate fixtures to include reviews and author; otherwise MountedGithubMergeGate now throws a capability error and the default-gate test cannot reach its expected READY result.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/github/merge-gate.ts, line 315:
<comment>Update the existing mounted merge-gate fixtures to include `reviews` and `author`; otherwise `MountedGithubMergeGate` now throws a capability error and the default-gate test cannot reach its expected `READY` result.</comment>
<file context>
@@ -270,12 +304,16 @@ const mountedMergeGateFields = (
['headRefOid', headRefOid],
['reviewDecision', reviewDecision],
['statusCheckRollup', Array.isArray(statusCheckRollup) ? statusCheckRollup : undefined],
+ ['reviews', Array.isArray(reviews) ? reviews : undefined],
+ ['author', author],
].flatMap(([name, value]) => value === undefined ? [name] : [])
</file context>

…ture
CI's `package` job failed: defaultMergeGate.check() now requires reviews
and author in the mounted PR projection (factory#432 part a), and this
fixture was missing both.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2QvRkHKvzpduLuDFRipaF
Session-Id: 56185aca-7dc6-42c8-b69e-650394d744aa
@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai review

Requested for exact head ca472c3a4389fca128de8fa5e15c7980d512c96e.

@khaliqgant
khaliqgant merged commit 5a35e33 into mainSep 3, 2026
9 checks passed
@khaliqgant
khaliqgant deleted the lane/factory-432-merge-gate-review-at-head branch September 3, 2026 14:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@khaliqgant