GitHub Action for smart dismissal of pull request reviews when code owners modify files after approval or when reviewers approve their own changes.
When working with large monorepos, there is 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.
The Auto Unapprove Reviews GitHub Action solves this by providing granular control over review dismissals. It follows a simple but effective flow:
- Get PR information
- Check changed files
- Check team ownership
- Analyze review status
- Take appropriate action
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: step-security/auto-unapprove@v1
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- 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@v3
id: app-token
with:
app-id: ${{ vars.REVIEWS_APP_ID }}
private-key: ${{ secrets.REVIEWS_APP_PRIVATE_KEY }}
- name: Dismiss Stale Reviews
uses: step-security/auto-unapprove@v1
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.



