Skip to content
Open
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
141 changes: 141 additions & 0 deletions .github/workflows/reapprove-internal-prs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Automatically reapprove PRs from PriorLabs org members after new code is pushed to them.
#
# The branch protection rules dismiss all approvals when new code is pushed to a PR, to
# ensure the most recent push is reviewed and approved. This is important for external
# collaborators, but is extra overhead for internal PRs where we trust the authors. In
# this case, the workflow automatically approves the current version of the PR if a
# previous version was already approved.
name: Reapprove internal PRs

on:
pull_request_review:
types: [dismissed]

permissions:
pull-requests: write

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Workflow grants write permissions globally

Low Severity

This workflow sets pull-requests: write at the workflow level instead of defaulting to empty permissions and granting write only on the reapprove job. That regresses the repository convention that workflow-level permissions stay empty and each job opts in.

Fix in Cursor Fix in Web

Triggered by learned rule: GHA release workflows: SHA-pin, no interpolation, bot token

Reviewed by Cursor Bugbot for commit e8b0f74. Configure here.


# One push can dismiss several approvals, and each dismissal starts its own run. Run
# them one at a time per PR, so that a later run sees the approval an earlier one
# created instead of adding a duplicate.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: false
# We need to check every review that was dismissed, in case it was an approval.
queue: max

jobs:
reapprove:
name: Restore dismissed approval
# The PR does not come from a fork. This also means the author has write access to
# the repository, as otherwise they could not have pushed the branch. The next step
# checks the author's org membership as well, for defence in depth.
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-slim
steps:
- name: Check the author and the reviewer are org members
id: membership
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
# We cannot read the memberships with GITHUB_TOKEN.
github-token: ${{ secrets.PRIORLABS_ORG_MEMBERSHIP_READ_PAT }}
script: |
const org = context.repo.owner;

async function isOrgMember(username) {
try {
await github.rest.orgs.checkMembershipForUser({ org, username });
return true;
} catch (error) {
if (error.status === 404) {
return false;
}
throw error;
}
}

const author = context.payload.pull_request.user.login;
if (!(await isOrgMember(author))) {
core.info(`The author ${author} is not an org member, not approving.`);
return;
}

// Without this check, an external user can trigger this bot to approve a PR
// by submitting an approval themselves. Not a big risk, as the PR's author
// must still be an internal user. We include 'github-actions[bot]' so that
// the workflow can reapprove again after a second push from the user.
const reviewer = context.payload.review.user.login;
if (reviewer !== "github-actions[bot]" && !(await isOrgMember(reviewer))) {
core.info(`The reviewer ${reviewer} is not an org member, not approving.`);
return;
}

core.setOutput("trusted", "true");

- name: Restore the dismissed approval
if: steps.membership.outputs.trusted == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { owner, repo } = context.repo;
const pullNumber = context.payload.pull_request.number;
const headSha = context.payload.pull_request.head.sha;
const dismissedReview = context.payload.review;

const reviews = await github.paginate(github.rest.pulls.listReviews, {
owner,
repo,
pull_number: pullNumber,
per_page: 100,
});

// Check if any of the reviews of this PR already approved the head commit.
// They might come from a human or an earlier run of the workflow.
// In this case, exit early to avoid approving again.
if (reviews.some((r) => r.commit_id === headSha && r.state === "APPROVED")) {
core.info("The current head is already approved, not approving again.");
return;
}

// The payload to this workflow doesn't tell us if the dismissed review was
// previously an approval, or why it was dismissed. To find this out, we
// find the review_dismissed event in the PR timeline.
const timeline = await github.paginate(
github.rest.issues.listEventsForTimeline,
{ owner, repo, issue_number: pullNumber, per_page: 100 },
);
const dismissal = timeline
.filter((e) => e.event === "review_dismissed")
.map((e) => e.dismissed_review)
.find((d) => d?.review_id === dismissedReview.id);
if (!dismissal) {
core.info(`Found no dismissal of review ${dismissedReview.id}.`);
return;
}

// The dismissed review may not have been an approval (e.g. if it requested
// changes), in which case we don't want to approve.
if (dismissal.state !== "approved") {
core.info("The dismissed review was not an approval, not approving.");
return;
}
// We only want to reapprove when the dismissal was due to new code being
// pushed, not if the reviewer manually dismissed the review.
if (!dismissal.dismissal_commit_id) {
core.info("The approval was dismissed by hand, not restoring it.");
return;
}

core.info("Approving to restore the dismissed approval.");
// This approves the current HEAD, not the commit "headSha" that triggered
// this workflow. This avoids a race condition when someone makes two pushes
// in quick succession, as the workflow would only be triggered for the
// first one. It's safe, because the push can only come from an org member.
await github.rest.pulls.createReview({
owner,
repo,
pull_number: pullNumber,
event: "APPROVE",
body:
"Reapproving after a push, as this is a PR from an internal author " +
"and was already approved."
});
Loading