Skip to content

ci: Add PR review enforcement GitHub Action#490

Open
john-weiler wants to merge 1 commit intomainfrom
add-review-enforcement
Open

ci: Add PR review enforcement GitHub Action#490
john-weiler wants to merge 1 commit intomainfrom
add-review-enforcement

Conversation

@john-weiler
Copy link
Contributor

@john-weiler john-weiler commented Feb 11, 2026

User description

Summary

  • Adds the review-enforcement.yml workflow from the API repo
  • Enforces that PRs have at least one approval from a non-bot reviewer (excludes baz-reviewer)
  • Auto-requests review from a random rungalileo/product team member when Dependabot PR CI fails

Test plan

  • Verify the workflow triggers on PR open/sync/reopen and review submit/dismiss events
  • Confirm Dependabot PRs are skipped for review enforcement
  • Confirm CI failure on Dependabot PRs triggers reviewer assignment

🤖 Generated with Claude Code


Generated description

Below is a concise technical summary of the changes proposed in this PR:
Implements a new GitHub Action workflow to enforce pull request review policies and automate reviewer assignment for failing Dependabot builds. Ensures that all non-Dependabot PRs receive at least one approval from a human reviewer who is not baz-reviewer.

TopicDetails
Review Enforcement Enforces a policy requiring at least one approval from a non-bot user (excluding baz-reviewer) for standard pull requests.
Modified files (1)
  • .github/workflows/review-enforcement.yml
Checks (1)
  • pre-commit / pre-commit
Latest Contributors(0)
UserCommitDate
Dependabot Support Automatically requests a review from a random member of the rungalileo/product team when a Dependabot PR fails its CI tests.
Modified files (1)
  • .github/workflows/review-enforcement.yml
Checks (1)
  • pre-commit / pre-commit
Latest Contributors(0)
UserCommitDate
This pull request is reviewed by Baz. Review like a pro on (Baz).

Adds the review-enforcement workflow from the API repo to enforce
that PRs have at least one approval from a non-bot reviewer, and
to auto-request reviews from the product team when Dependabot CI fails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 11, 2026 17:25
@john-weiler john-weiler changed the title Add PR review enforcement GitHub Action ci: Add PR review enforcement GitHub Action Feb 11, 2026
@codecov
Copy link

codecov bot commented Feb 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@67b6974). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #490   +/-   ##
=======================================
  Coverage        ?   71.29%           
=======================================
  Files           ?       71           
  Lines           ?     5947           
  Branches        ?     1480           
=======================================
  Hits            ?     4240           
  Misses          ?     1697           
  Partials        ?       10           
Flag Coverage Δ
unittests 71.29% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a GitHub Actions workflow to enforce PR review requirements and to auto-request review on Dependabot CI failures, aligning this repo’s PR process with the API repo’s policy.

Changes:

  • Introduces review-enforcement.yml workflow to enforce a minimum approval rule (excluding baz-reviewer).
  • Skips enforcement for Dependabot PRs.
  • On failed “Run Tests” workflow runs for Dependabot PRs, requests review from a random rungalileo/product team member.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

);

if (!hasNonBazApproval) {
core.setFailed('At least one approval from a non-baz-reviewer is required.');
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The failure message says “non-baz-reviewer”, but the logic also enforces “non-bot” via r.user.type === 'User'. Consider updating the message to reflect the actual requirement (human approval, excluding baz-reviewer) so it’s actionable for contributors.

Suggested change
core.setFailed('At least one approval from a non-baz-reviewer is required.');
core.setFailed("At least one approval from a human reviewer other than 'baz-reviewer' is required.");

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +25
const pr = context.payload.pull_request;
const event = context.eventName;
const isDependabot = pr.user?.login === 'dependabot[bot]';

Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

enforce-review runs for workflow_run events too, but this script assumes context.payload.pull_request exists. On workflow_run, pull_request is undefined, so pr.user?.login will throw before the optional chain is evaluated. Add a job-level if: github.event_name != 'workflow_run' (or handle workflow_run explicitly by resolving the PR from context.payload.workflow_run.pull_requests and fetching PR data).

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +44
const pr = context.payload.pull_request;
const event = context.eventName;
const isDependabot = pr.user?.login === 'dependabot[bot]';

// Skip for Dependabot on all events
if (isDependabot) {
core.info('Dependabot PR detected - skipping review enforcement.');
return;
}

// Only enforce review requirement on pull_request_review events
if (event === 'pull_request') {
core.info('PR opened/synchronized - waiting for review, not enforcing yet.');
return;
}

// From here on, we know it's a pull_request_review event (submitted or dismissed): enforce rule
core.info('Review event detected - checking current approval status...');

const reviews = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

On pull_request (opened/synchronize/reopened) this job exits successfully without checking approvals. If this workflow is used as a required status check, it can allow merging without approval and also won’t re-validate after new commits (when approvals may be dismissed). Consider running the approval check on pull_request events as well and failing when no valid approval exists.

Suggested change
const pr = context.payload.pull_request;
const event = context.eventName;
const isDependabot = pr.user?.login === 'dependabot[bot]';
// Skip for Dependabot on all events
if (isDependabot) {
core.info('Dependabot PR detected - skipping review enforcement.');
return;
}
// Only enforce review requirement on pull_request_review events
if (event === 'pull_request') {
core.info('PR opened/synchronized - waiting for review, not enforcing yet.');
return;
}
// From here on, we know it's a pull_request_review event (submitted or dismissed): enforce rule
core.info('Review event detected - checking current approval status...');
const reviews = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number
const event = context.eventName;
const { owner, repo } = context.repo;
let prNumber;
let prUserLogin;
if (event === 'pull_request' || event === 'pull_request_review') {
const pr = context.payload.pull_request;
if (!pr) {
core.setFailed('Pull request context not found for event: ' + event);
return;
}
prNumber = pr.number;
prUserLogin = pr.user?.login;
} else if (event === 'workflow_run') {
const workflowRun = context.payload.workflow_run;
const associatedPr = workflowRun?.pull_requests?.[0];
if (!associatedPr) {
core.info('Workflow run is not associated with a pull request - skipping review enforcement.');
return;
}
prNumber = associatedPr.number;
const prResponse = await github.rest.pulls.get({
owner,
repo,
pull_number: prNumber
});
prUserLogin = prResponse.data.user?.login;
} else {
core.info(`Event '${event}' is not supported by this workflow - skipping review enforcement.`);
return;
}
const isDependabot = prUserLogin === 'dependabot[bot]';
// Skip for Dependabot on all events
if (isDependabot) {
core.info('Dependabot PR detected - skipping review enforcement.');
return;
}
core.info('Checking current approval status for pull request #' + prNumber + '...');
const reviews = await github.rest.pulls.listReviews({
owner,
repo,
pull_number: prNumber

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +51
const approvals = reviews.data.filter(r => r.state === 'APPROVED');
const hasNonBazApproval = approvals.some(
r => r.user?.login &&
r.user.login !== 'baz-reviewer' &&
r.user.type === 'User'
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The approval check counts any review with state APPROVED, even if the same reviewer later requested changes or the approval was dismissed. GitHub review state is effectively “latest review per user”, so this can incorrectly pass. Compute each reviewer’s latest review state (e.g., by sorting by submitted_at and keeping the most recent per user.login) before deciding whether an approval exists.

Suggested change
const approvals = reviews.data.filter(r => r.state === 'APPROVED');
const hasNonBazApproval = approvals.some(
r => r.user?.login &&
r.user.login !== 'baz-reviewer' &&
r.user.type === 'User'
// Determine the latest review per user (GitHub treats review state as latest review per user)
const latestReviewsByUser = new Map();
for (const r of reviews.data) {
const user = r.user;
const login = user?.login;
if (!login || user.type !== 'User') {
continue;
}
const existing = latestReviewsByUser.get(login);
if (
!existing ||
(r.submitted_at && new Date(r.submitted_at) > new Date(existing.submitted_at))
) {
latestReviewsByUser.set(login, r);
}
}
const approvals = Array.from(latestReviewsByUser.values()).filter(
r => r.state === 'APPROVED'
);
const hasNonBazApproval = approvals.some(
r => r.user?.login && r.user.login !== 'baz-reviewer'

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +47
const reviews = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number
});

const approvals = reviews.data.filter(r => r.state === 'APPROVED');
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

pulls.listReviews is paginated (defaults to 30). For PRs with many reviews, the required approval could be on a later page and be missed. Set per_page: 100 and paginate through all pages (or use Octokit pagination helpers) before evaluating approvals.

Suggested change
const reviews = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number
});
const approvals = reviews.data.filter(r => r.state === 'APPROVED');
const reviews = await github.paginate(
github.rest.pulls.listReviews,
{
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
per_page: 100
}
);
const approvals = reviews.filter(r => r.state === 'APPROVED');

Copilot uses AI. Check for mistakes.
enforce-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

This job only reads PR data; it doesn’t perform any PR write operations. Consider reducing permissions from pull-requests: write to pull-requests: read here to follow least-privilege.

Suggested change
pull-requests: write
pull-requests: read

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +45
const pr = context.payload.pull_request;
const event = context.eventName;
const isDependabot = pr.user?.login === 'dependabot[bot]';

// Skip for Dependabot on all events
if (isDependabot) {
core.info('Dependabot PR detected - skipping review enforcement.');
return;
}

// Only enforce review requirement on pull_request_review events
if (event === 'pull_request') {
core.info('PR opened/synchronized - waiting for review, not enforcing yet.');
return;
}

// From here on, we know it's a pull_request_review event (submitted or dismissed): enforce rule
core.info('Review event detected - checking current approval status...');

const reviews = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number
});
Copy link

Choose a reason for hiding this comment

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

context.payload.pull_request is undefined on workflow_run events, but the script reads pr.user?.login before checking the event type, so the job will throw before reaching the guard whenever the workflow_run trigger fires; can we guard against missing pull_request (or skip workflow_run) before dereferencing pr?

Finding type: Logical Bugs

Prompt for AI Agents:

In .github/workflows/review-enforcement.yml around lines 22 to 45, the script assigns
const pr = context.payload.pull_request and then immediately reads pr.user?.login, but
on workflow_run events payload.pull_request is undefined causing a crash. Change the
logic to first check the event type and/or the existence of context.payload.pull_request
before dereferencing pr: either move the event/type check above the isDependabot check
or add a guard like if (!context.payload.pull_request) { core.info('No pull_request in
payload - skipping'); return; } so workflow_run runs don’t throw when pull_request is
missing.

Fix in Cursor

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