GitHub Action for smart dismissal of pull request reviews when code owners modify files after approval or when reviewers approve their own changes.
As a DevOps engineer working with large monorepos, I've often encountered a frustrating limitation in GitHub's PR review system. When new commits are pushed to a PR, GitHub only offers two options:
- keep all approvals
- dismiss all approvals. This binary choice becomes particularly problematic in monorepos where multiple teams own different parts of the codebase
To solve this, I developed a GitHub Action called "Auto Unapprove Reviews" that provides granular control over review dismissals. Here's how it works:
The action follows a simple but effective flow:
- Get PR information
- Check changed files
- Check team ownership
- Analyze review status
- Take appropriate action
dismiss-reviews/
βββ π― **Core Files**
β βββ src/index.js # GitHub Action entry point
β βββ action.yml # Action definition
β βββ package.json # Dependencies
β
βββ π **Main Script**
β βββ auto-unapprove.js # β Smart dismissal with stale detection
β
βββ π **Examples & Documentation**
β βββ examples/basic-usage.md # Usage examples and setup
β βββ examples/workflow-example.yml # Sample GitHub Actions workflow
β
βββ π§ͺ **Demo & Testing**
β βββ test-team-info.js # Demo of team information features
β
βββ π¦ **Build Artifacts**
βββ dist/ # Compiled action bundle
auto-unapprove.js - Smart dismissal with advanced features:
- β Stale approval detection - Dismisses approvals when files are modified after approval
- β Team membership validation - Supports GitHub team-based code ownership
- β Precise file matching - Only dismisses when owned files are actually modified
- β Performance optimized - Parallel API calls and efficient caching
- β Comprehensive logging - Detailed analysis and reasoning for each decision
- name: Smart dismiss reviews
uses: ./
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
pr-number: ${{ github.event.number }}
dry-run: 'false'
code-owners-file: 'CODEOWNERS' # Optional: custom path
target-branch: ${{ github.event.pull_request.base.ref }} # Optional: target branch
team-start-with: '@' # Optional: team prefix# Set environment variables
export GITHUB_TOKEN="your_token"
export PR_NUMBER="123"
export GITHUB_REPOSITORY="myorg/myrepo"
# Run smart dismissal
node auto-unapprove.js- Get all changed files from the entire PR (not just latest commit)
- Parse CODEOWNERS from the PR target branch (not default branch) using hierarchical path matching (most specific wins)
- Check team memberships via GitHub API for relevant teams only
- Analyze approval timeline - detect commits made after approval
- Smart dismissal logic:
- Dismiss code owners who authored commits
- Dismiss stale approvals (post-approval commits to owned files)
- Preserve legitimate approvals from non-owners
name: Auto Unapprove
on:
pull_request:
types: [synchronize]
jobs:
auto-unapprove:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ vars.REVIEWS_APP_ID }}
private-key: ${{ secrets.REVIEWS_APP_PRIVATE_KEY }}
- name: Dismiss Stale Reviews
uses: RotemK1/auto-unapprove@main
with:
github-token: ${{ steps.app-token.outputs.token }}
pr-number: ${{ github.event.number }}
dry-run: 'false'
target-branch: ${{ github.event.pull_request.base.ref }}
team-start-with: '@your-org/'For more workflow examples, see example-workflow.yml
Required Permissions: for more info on how to use GitHub App token check https://github.com/actions/create-github-app-token
-
Repository permissions:
- Administration: Repository creation, deletion, settings, teams, and collaborators. (READ ONLY)
- Contents: Repository contents, commits, branches, downloads, releases, and merges. (READ ONLY)
- Pull requests: Pull requests and related comments, assignees, labels, milestones, and merges. (READ AND WRITE)
-
Organization permissions:
- Members: Organization members and teams
- β Target branch CODEOWNERS: Reads ownership rules from PR target branch, not default branch
- β Stale approval detection: Automatically detects and dismisses approvals made stale by subsequent commits
- β Surgical precision: Only dismisses when owned files are actually modified
- β Team support: Full GitHub team membership validation via API
- β Timeline analysis: Compares approval timestamps with commit timestamps
- β Hierarchical CODEOWNERS: Proper path matching with most-specific-wins logic
- β Performance optimized: Parallel API calls and efficient team checking
- β Comprehensive logging: Detailed reasoning for every dismissal decision
- β Dry-run mode: Safe testing without actual dismissals
π Smart Review Dismissal
Repository: myorg/myrepo
PR: #123
Mode: π§ͺ DRY RUN
π All changed files in PR (3):
π src/gui/components/Button.tsx
π src/gui/styles/theme.css
π src/gui/utils/helpers.ts
π― Target branch: main
π Parsed 15 CODEOWNERS rules
π― DISMISSAL ANALYSIS:
π Checking commits after jane-smith's approval (2025-06-04T12:49:59.000Z)...
π
Commit 55a4fc5 at 2025-06-04T12:53:44Z
π― Modified owned files: src/gui/components/Button.tsx
π« DISMISS @jane-smith
π Files: src/gui/components/Button.tsx, src/gui/styles/theme.css, src/gui/utils/helpers.ts
π Owner Via: @myorg/frontend-team
π‘ Reason: Approval became stale - commits modified owned files
β
KEEP @bob-jones
π Not owner of changed files
π EXECUTION PLAN:
β’ Changed files: 3
β’ Total approvals: 2
β’ Dismissals needed: 1
β’ Approvals preserved: 1| Input | Required | Default | Description |
|---|---|---|---|
github-token |
β | - | GitHub API token with repo access |
pr-number |
β | - | Pull request number to analyze |
dry-run |
- | true |
Set to 'false' for actual dismissals |
code-owners-file |
- | CODEOWNERS |
Path to CODEOWNERS file |
target-branch |
- | main |
Target branch to read CODEOWNERS from |
team-start-with |
- | @ |
Team prefix for organization |
| Variable | Required | Default | Description |
|---|---|---|---|
GITHUB_TOKEN |
β | - | GitHub API token with repo access |
PR_NUMBER |
β | - | Pull request number to analyze |
GITHUB_REPOSITORY |
β | - | Repository in owner/repo format |
TEAM_START_WITH |
- | @ |
Team prefix for organization |
DRY_RUN |
- | true |
Set to 'false' for actual dismissals |
CODEOWNERS_FILE |
- | CODEOWNERS |
Path to CODEOWNERS file |
TARGET_BRANCH |
- | main |
Target branch to read CODEOWNERS from |
CHANGED_FILES |
- | - | Newline-separated files (webhook optimization) |
The project includes comprehensive tests for the pagination implementation:
./tests/run-tests.shexport GITHUB_TOKEN='your_token'
export GITHUB_REPOSITORY='owner/repo'
export PR_NUMBER='123'
export DRY_RUN='true'
./tests/test-real-pagination.shtests/test-pagination.js- Logic tests with simulated datatests/test-mock-pagination.js- Mock API teststests/test-real-pagination.sh- Real GitHub API teststests/TESTING.md- Comprehensive testing guide
For detailed testing instructions, see tests/README.md.
This is an open source project. Any contribution would be greatly appreciated!
If you have found an issue, please report it on the issue tracker


