Skip to content

Enforce cross-model review coverage on AI-authored PRs - #2552

Merged
kriszyp merged 1 commit into
mainfrom
kris/review-coverage-enforce
Sep 11, 2026
Merged

kriszyp merged 1 commit into
mainfrom
kris/review-coverage-enforce

Conversation

@kriszyp

@kriszyp kriszyp commented Sep 9, 2026

Copy link
Copy Markdown
Member

The review-coverage check moves from report to enforce, and to a trusted host so that enforcement means something: it runs on pull_request_target and checks out the base branch, so a PR cannot edit the check that gates it. An AI-authored, non-draft, member PR changing more than two lines now goes red unless its description reports two distinct outside-model review families.

For the human reviewer

  1. Every currently-open AI-authored PR under two families goes red on its next edit or synchronize. Replaying the new logic over all 163 harper and harper-pro PRs from the last two weeks: 61 would fail — 41 reporting one family, 14 AI-signed but never reviewed, 6 with no valid receipt. The single largest cause is an expired agy (Gemini) login: 28 of the 52 sub-threshold AI PRs report blocked=gemini(auth) or a sibling failure. This is deliberate — you asked for red as the forcing function to get people authenticated — but it is a large first-day number and it is the thing to overrule if it is too much. Reverting is mode: report.
  2. Only the receipt-derived Review-Coverage: footer satisfies enforce. A hand-written ## Review coverage section is still counted and shown, but never accepted: it is written from memory, and it cannot exclude the authoring family when the body carries no generator signature, so a Claude PR listing "Claude: clean" scored itself. Only 1 of the 163 PRs relies on prose, so the blast radius is small — but there is no deprecation window.
  3. Enforce lands in the open-source repo while the footer generator lives in a private one. An org member on a checkout without skills-internal gets a red check they cannot self-service from the tooling. Mitigated by putting the footer's grammar in the check text and the README so it can be written by hand; the alternative is enforcing in harper-pro first and leaving core at report.
  4. The easy waiver is size-only here, deliberately. The pre-push gate additionally refuses the waiver when the diff touches a risk surface; CI cannot compute that without the file list, which harper-pro does not collect. So a 149-line change to security/ waives its second lens in CI. The caps are action inputs, so tightening is config; removing the waiver is not.
  5. The gate reads the PR body, which the author can edit. Deleting the generator signature and the HEG fields makes a PR read as not-AI-authored and exempt. Commit trailers would close it but need another API call and input plumbing. Recorded under "Known limits" in the README — this is a guardrail against forgetting a leg, not a control against someone determined to bypass it.
  6. ref: base.ref, not base.sha. The action tracks the base branch tip rather than the commit the PR forked from, so a long-lived PR gets the current check rather than a stale one — at the cost of the action moving under it. One line either way.

Changes

Trusted host. .github/workflows/review-coverage.yml swaps pull_request for pull_request_target and checks out the base branch ref, never the PR head, with mode: enforce on the action. No PR code executes: coverage reads the event payload and the file list comes from the API. Permissions stay read-only. A new test in prFormat.test.mjs asserts that pairing directly — drop the ref: line and the job would check out fork-PR head and run attacker-supplied ci-review-coverage.mjs under the base repo's token, and nothing else in the suite would notice.

AI-authorship detection. isAiAuthored in prExemption.mjs recognizes a Complexity: easy|medium|complicated grade and the two footer fields, or a line beginning Generated with/by <tool> or a Co-Authored-By: trailer naming one. The signature forms are line-anchored so ordinary prose does not gate a hand-written PR, and they do not route through generatorFamily, which returns nothing unless exactly one family matches — "Generated with Cursor Grok" names two and must still count. Complexity: requires a real grade, so an index PR whose description says Complexity: O(n log n) is not classified as an agent PR.

Fence handling. Both markers-readers now share stripFencedBlocks from prFormatLinks.mjsthe coverage evaluator strips fences the same way —, which normalizes CRLF and handles ~~~, 4+-backtick, indented and blockquoted fences. The hand-rolled three-backtick regex it replaces was a no-op on the CRLF bodies GitHub's web editor produces, so a PR documenting this convention had its own example read as live coverage.

Last-wins, consistently. The coverage footer and the Complexity: field are both read last-first, matching Human-Review-Need. Reading count off the prose scan's first match while gating on the last footer produced a red check whose summary said two reviews ran, and a green one whose detail said none did. A footer with no recognized authored= is not counted at all — without it the parser cannot exclude the authoring family, so ran=claude,codex on a Claude PR scored two.

The waiver. easyDiffWaiver corroborates Complexity: easy against the diff: at most easy_max_lines (150) and easy_max_files (10), both failing closed when the measurement is missing, and it names which cap bound. It waives one leg rather than all but one, so a consumer setting required: 3 still gets two. The caps are action inputs; setting either to 0 disables the waiver. The remediation text and the README both carry the footer's grammar, so an org member without the private skills checkout can act on a red check.

Verification

Route: unit tests over the evaluator and the CLI entry point, plus a replay of the real PR population. This workflow cannot check itself — swapping the trigger means pull_request is no longer registered for this head and the base branch's copy still says pull_request, so no run appears until after merge. main has no required status checks, so nothing waits on it.

  • node --test .github/actions/review-coverage/*.test.mjs — 101/101, with 20 new tests covering two live footers in both orders, the authorless footer, the last-Complexity:-wins case, Complexity: O(n log n) staying human-authored, the waiver arithmetic at required: 3, both fence-form bypasses, CRLF bodies, and the INPUT_EASY_MAX_* wiring.
  • npx prettier --check and npx oxlint on every changed file — clean. Prettier caught a real break: an unquoted `Complexity: easy` inside a YAML description made action.yml unparseable.
  • Replayed evaluateCiCoverage and classifyPullRequest at mode: enforce over all 163 harper and harper-pro PRs created since 2026-08-26, fetched from the API with real additions/deletions/changed_files/author_association. Result is the 61-red breakdown in item 1; the easy waiver rescues 13. Script kept at ~/dev/scripts/review-coverage-audit.mjs.

Three rounds of cross-model review ran on this branch. Round 2's major was the weak fence regex; round 3 found no majors. Remaining minors are the ledger items above.

Complexity: complicated

Review-Coverage: authored=claude; ran=gemini,cursor-grok,codex; adjudicated=domain; declined=cursor-composer; rounds=3 @ 61c53b0

Human-Review-Need: 4 @ 61c53b0

The review-coverage check moves from report to enforce, and to a trusted host
so enforcement means something: it runs on pull_request_target and checks out
the base commit, so a PR cannot edit the check that gates it. Nothing from the
PR is executed — coverage reads the event payload and the file list comes from
the API.

Two changes to what it measures:

AI-authorship is now detected by generator signature as well as by the HEG
footer fields. Over the last two weeks of harper and harper-pro PRs, 14 of the
15 member PRs carrying no HEG field carried "Generated with Claude Code", so
keying on the fields alone exempted most of the population the gate is for.

`Complexity: easy` waives the second review leg on a narrow diff — at most 150
changed lines and 10 files, and at least one outside review still reported. The
diff has to corroborate the claim: the field is written by the agent grading its
own work, and pr-conventions guarantees it may only raise the diff-derived
grade, so letting `easy` waive a gate unconditionally would invert that
incentive.

The `@ <sha>` pin on the footers is reported but never gated on. The question is
whether two outside models looked at the change, not whether the footer was
re-materialized after the last amend.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a "Complexity: easy" waiver mechanism to the review coverage GitHub Action, allowing AI-authored PRs to waive one required review leg if the diff is small (based on customizable line and file thresholds). It also enhances AI-authorship detection, blanks fenced code blocks to prevent self-matching, and adds comprehensive tests for these features and workflow security. There are no review comments to address, and the implementation aligns with the repository's style guidelines.

@kriszyp
kriszyp requested a review from a team September 9, 2026 22:18
@kriszyp
kriszyp marked this pull request as ready for review September 9, 2026 22:20
@claude

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found.

kriszyp added a commit to HarperFast/harper-pro that referenced this pull request Sep 10, 2026
…r intact

The enforce flip rewrote this file's header and dropped two things harper-pro's
main has carried since #706: the job's `name:`, which is the published check
context (`review-coverage` — without it the context becomes the job id,
`coverage`), and the pin's trailing `# main` comment. Renovate's github-actions
manager takes the tracked ref from that comment and needs a single bare token,
so the prose that replaced it left the pin as an unversioned reference: never
tracked, never bumped. Both are restored.

The header keeps the new no-checkout invariant and states what the check
actually validates, which the flip had replaced with Actions mechanics, and
records why `edited` belongs in the trigger list.

Merge ordering against HarperFast/harper#2552 moves to a `Depends-on:` marker in
the PR body, which holds the required `companion-check` status pending until the
companion merges, rather than to a pin comment nothing enforces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kriszyp added a commit to HarperFast/harper-pro that referenced this pull request Sep 11, 2026
* Enforce cross-model review coverage on AI-authored PRs

harper-pro had no review-coverage check at all, so the two-cross-model-review
policy was only visible on harper. Pin harper's public copy of the action by
sha and run it in enforce mode.

Coverage only: format_mode stays off, so this reports and gates the
`Review-Coverage:` footer without also gating description structure.

Runs on pull_request_target with a base-ref action pin, so a PR cannot edit the
check that gates it; nothing from the PR is executed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Keep the review-coverage check's published context and Renovate marker intact

The enforce flip rewrote this file's header and dropped two things harper-pro's
main has carried since #706: the job's `name:`, which is the published check
context (`review-coverage` — without it the context becomes the job id,
`coverage`), and the pin's trailing `# main` comment. Renovate's github-actions
manager takes the tracked ref from that comment and needs a single bare token,
so the prose that replaced it left the pin as an unversioned reference: never
tracked, never bumped. Both are restored.

The header keeps the new no-checkout invariant and states what the check
actually validates, which the flip had replaced with Actions mechanics, and
records why `edited` belongs in the trigger list.

Merge ordering against HarperFast/harper#2552 moves to a `Depends-on:` marker in
the PR body, which holds the required `companion-check` status pending until the
companion merges, rather than to a pin comment nothing enforces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@kriszyp
kriszyp merged commit a9961c5 into main Sep 11, 2026
41 of 46 checks passed
@kriszyp
kriszyp deleted the kris/review-coverage-enforce branch September 11, 2026 22:37
Sign up for free to 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.

2 participants