-
Notifications
You must be signed in to change notification settings - Fork 57
Approve uncontested PRs after a week #1669
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,80 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: Auto-approve uncontested PRs after 7 days | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| schedule: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - cron: '26 5 * * *' # Run every day at 5:26 AM UTC (time chosen randomly to avoid creating peaks) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workflow_dispatch: # Allow manual triggering | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pull-requests: write | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| approve-uncontested-prs: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Auto-approve uncontested PRs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: actions/github-script@v7 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| script: | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Calculate the date 7 days ago | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sevenDaysAgo = new Date(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fetch all open pull requests, sorted by last update (oldest first) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data: pullRequests } = await github.rest.pulls.list({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| owner: context.repo.owner, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repo: context.repo.repo, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state: 'open', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sort: 'updated', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| direction: 'asc' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const pr of pullRequests) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Skip draft PRs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (pr.draft) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`Skipping PR #${pr.number}: Draft PR`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Use updated_at instead of created_at to ensure we don't auto-approve | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // code that was just pushed to an old PR | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const lastActivity = new Date(pr.updated_at); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Only process PRs that haven't been updated in 7+ days | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (lastActivity < sevenDaysAgo) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fetch reviews to check for objections and prior bot approval | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data: reviews } = await github.rest.pulls.listReviews({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| owner: context.repo.owner, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repo: context.repo.repo, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pull_request_number: pr.number | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if any reviewer has requested changes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hasChangesRequested = reviews.some( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| review => review.state === 'CHANGES_REQUESTED' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (hasChangesRequested) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`Skipping PR #${pr.number}: Has changes requested`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+52
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logic bug: Historical The GitHub API returns all reviews in history. If a reviewer requested changes but later approved (or their review was dismissed), both reviews appear in the list. The current logic will skip the PR because it finds a historical Check only the most recent review per reviewer: 🐛 Proposed fix to check only latest review per reviewer- // Check if any reviewer has requested changes
- const hasChangesRequested = reviews.some(
- review => review.state === 'CHANGES_REQUESTED'
- );
+ // Get the most recent review state per reviewer
+ const latestReviewByUser = new Map();
+ for (const review of reviews) {
+ if (review.user && review.state !== 'COMMENTED') {
+ latestReviewByUser.set(review.user.login, review.state);
+ }
+ }
+
+ // Check if any reviewer's latest review requested changes
+ const hasChangesRequested = [...latestReviewByUser.values()].some(
+ state => state === 'CHANGES_REQUESTED'
+ );📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if already approved by this action to avoid spamming | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const alreadyApproved = reviews.some( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| review => review.user.login === 'github-actions[bot]' && review.state === 'APPROVED' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+62
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add null safety for If a reviewer's account has been deleted, 🛡️ Proposed fix with null check // Check if already approved by this action to avoid spamming
const alreadyApproved = reviews.some(
- review => review.user.login === 'github-actions[bot]' && review.state === 'APPROVED'
+ review => review.user?.login === 'github-actions[bot]' && review.state === 'APPROVED'
);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!alreadyApproved) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Submit an approval review | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await github.rest.pulls.createReview({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| owner: context.repo.owner, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repo: context.repo.repo, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pull_request_number: pr.number, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body: '✅ Auto-approving: This PR has been inactive for 7+ days with no objections.', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event: 'APPROVE' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`Approved PR #${pr.number}: ${pr.title}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing pagination handling will skip PRs in repositories with many open PRs.
The
pulls.listAPI returns only 30 results by default (max 100 per page). If the repository has more open PRs than that, older PRs beyond the first page will never be processed.Use
github.paginateto iterate through all pages:🔧 Proposed fix using pagination
📝 Committable suggestion
🤖 Prompt for AI Agents