Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
* @rungalileo/product
128 changes: 128 additions & 0 deletions .github/workflows/review-enforcement.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Enforce Review Policy

on:
pull_request:
types: [opened, synchronize, reopened]
pull_request_review:
types: [submitted, dismissed]
workflow_run:
workflows: ["Test"]
types: [completed]

jobs:
enforce-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Enforce review policy
uses: actions/github-script@v7
with:
script: |
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;
}
Comment on lines +22 to +30
Copy link
Contributor

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 immediately runs const isDependabot = pr.user?.login === 'dependabot[bot]', which throws before the job can exit; can we guard for workflow_run (or restrict the job to pull_request* events) before accessing pr.user?

Finding type: Logical Bugs

Prompt for AI Agents:

In .github/workflows/review-enforcement.yml around lines 22 to 30, the script reads
const pr = context.payload.pull_request and immediately uses pr.user, which throws when
the job runs on workflow_run events where pull_request is undefined. Update the logic to
first check the event type (e.g., if context.eventName === 'workflow_run' or if
context.payload.pull_request is falsy) and return early before accessing pr.user;
alternatively, add a guard like if (!pr) { core.info('No pull request in payload -
skipping.'); return; } so the script never dereferences pr.user when pr is undefined.

Fix in Cursor


// 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 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'
);

if (!hasNonBazApproval) {
core.setFailed('At least one approval from a non-baz-reviewer is required.');
}
Comment on lines +41 to +56
Copy link
Contributor

Choose a reason for hiding this comment

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

pulls.listReviews returns every historical review event, including approvals that were later dismissed/changed, but this code just filters for any state === 'APPROVED'; a reviewer can undo their approval (change request/dismissal) and the workflow still sees the old approval and passes. Can we reduce to each reviewer's latest review (e.g. sort submitted_at and drop older events) so only current approvals keep the run green?

Finding type: Logical Bugs

Prompt for AI Agents:

In .github/workflows/review-enforcement.yml around lines 41 to 56, the logic that
computes approvals uses reviews.data.filter(r => r.state === 'APPROVED') which can count
historical approvals that were later dismissed or changed. Refactor this block to first
group reviews.data by r.user.login (skip reviews with no user), for each reviewer keep
only their latest review event (compare submitted_at timestamps), then compute approvals
by checking latestReview.state === 'APPROVED' and hasNonBazApproval from those latest
reviews only. Ensure this handles missing submitted_at gracefully and continues to
ignore the 'baz-reviewer' user as before.

Fix in Cursor


dependabot-ci-failure:
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'failure'
permissions:
pull-requests: write
steps:
- name: Request review from random product team member on CI failure
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GALILEO_AUTOMATION_GITHUB_TOKEN }}
script: |
// Get the PR associated with this workflow run
const workflowRun = context.payload.workflow_run;

if (!workflowRun.pull_requests || workflowRun.pull_requests.length === 0) {
core.info('No pull request associated with this workflow run.');
return;
}

const prNumber = workflowRun.pull_requests[0].number;

// Get PR details
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});

const isDependabot = pr.user?.login === 'dependabot[bot]';

if (!isDependabot) {
core.info('PR is not from Dependabot - skipping CI failure notification.');
return;
}

core.info(`Dependabot PR #${prNumber} CI failed - requesting review from random product team member.`);

// Get members of the rungalileo/product team
let teamMembers;
try {
const { data: members } = await github.rest.teams.listMembersInOrg({
org: 'rungalileo',
team_slug: 'product',
});
teamMembers = members.map(m => m.login);
} catch (error) {
core.setFailed(`Failed to fetch team members: ${error.message}`);
return;
}

if (teamMembers.length === 0) {
core.setFailed('No members found in rungalileo/product team.');
return;
}

// Select a random team member
const randomMember = teamMembers[Math.floor(Math.random() * teamMembers.length)];
core.info(`Selected random reviewer: ${randomMember}`);

// Request review from the selected member
try {
await github.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
reviewers: [randomMember]
});
core.info(`Successfully requested review from ${randomMember} for PR #${prNumber}`);
} catch (error) {
core.setFailed(`Failed to request review: ${error.message}`);
}
Loading