Build a PR Reviewer Auto-Assigner
Build a custom GitHub Action that auto-assigns PR reviewers by file path.
Introduction
30 Second Summary
Every team has that one pull request that sits unreviewed for days because nobody knew they were supposed to look at it. Manually assigning reviewers slows down the whole development cycle.
In this project, you will build a custom GitHub Action that automatically assigns the right code reviewers to pull requests based on which files were changed.
What You'll Build
A JavaScript-based GitHub Action that reads changed files from a pull request, matches them against configurable path rules, and requests the appropriate reviewers via the GitHub REST API.
By the end of this project, you'll have:
- A complete custom GitHub Action with metadata, JavaScript source code, and bundled output ready to run on any repository.
- A reviewer configuration file that maps file path prefixes to reviewer usernames for automatic assignment.
- A working DevOps automation deployed on GitHub and triggered by a real pull request.
- Secret Mission: Add glob pattern matching and a fallback default reviewers rule for files that match no other pattern.
Are there any prerequisites?
None. This guide walks you through setting up every tool you need, including Node.js, npm, and a GitHub account.
Set Up Your Development Environment
Before you can build a custom GitHub Action, you need a JavaScript development environment and a repository to work in.
In this step, you will install Node.js, create a GitHub repository, and initialize your project so it is ready for coding.
In this step, get ready to:
- Install Node.js v24.18.0 LTS.
- Create and clone a GitHub repository.
- Initialize a Node.js project with npm.
Install Node.js
Node.js is the JavaScript runtime that powers GitHub Actions. You need version 24 or higher for this project.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open it.
- Check if Node.js is already installed by running this command:
node -v
✔️ I see version 24 or higher
Nice, Node.js is ready. Confirm npm is also available by running:
npm -v
You should see a version number printed. You are all set to move on.
ⓧ I see an older version
You have an older version of Node.js installed. The new installer will replace it with v24.18.0.
- Open your browser and go to https://nodejs.org/en/download.
- Download the LTS installer for your operating system (.pkg for macOS, .msi for Windows).
- Run the installer and follow the prompts, accepting the defaults.
- Close and reopen Terminal, then run node -v again. You should now see v24.18.0 or similar.
- Confirm npm is available by running:
npm -v
You should see a version number printed.
ⓧ Command not found
Node.js is not installed yet. Let's install v24.18.0 LTS now.
- Open your browser and go to https://nodejs.org/en/download.
- Download the LTS installer for your operating system (.pkg for macOS, .msi for Windows).
- Run the installer and follow the prompts, accepting the defaults.
- Close and reopen Terminal, then run node -v again. You should now see v24.18.0 or similar.
- Confirm npm is available by running:
npm -v
You should see a version number printed.
Still seeing an old version after installing?
Make sure you fully closed and reopened Terminal after the installation. The old terminal session still has the old path cached.
On macOS, if the system's built-in node is resolving first, try running /usr/local/bin/node -v to confirm the new version is installed.
Still stuck? Help me troubleshoot my Node.js installation.
Create and clone your GitHub repository
Your GitHub Action needs a repository to live in. You will create a public repository on GitHub and clone it to your local machine.
- If you do not have a GitHub account, create one for free at https://github.com/signup.
- Once signed in, click the + icon in the upper-right corner and select New repository.
- In the Repository name field, type pr-reviewer-action.
- Select Public for the visibility.
- Leave all other options unchecked (no README, no .gitignore, no license).
- Click Create repository.
Why no README?
Skipping the README keeps the repository completely empty. This avoids merge conflicts when you push your local files later. You will add all project files from your machine.
Now clone the repository to your Desktop so you have a local copy to work with.
- In your terminal, move to your Desktop by running this command:
cd ~/Desktop
- Clone the repository by running this command:
git clone [[REPO_URL="https://github.com/your-username/pr-reviewer-action.git"]]
Where do I find my repository URL?
After creating the repository, GitHub shows a Quick Setup page with your repository URL. Copy the HTTPS URL from there. It looks like https://github.com/your-username/pr-reviewer-action.git.
- Move into the cloned folder by running:
cd pr-reviewer-action
- Confirm you are in the correct folder by running:
ls
The folder will be empty since you did not initialize a README. That is expected.
Getting a permission or authentication error?
If git clone asks for credentials and then fails, make sure you are signed in to GitHub in your browser and that the repository name matches exactly.
On macOS, Git may prompt you to sign in through a browser window. Accept the prompt and try the clone again.
Need more help? Help me fix a git clone authentication error.
Open the project in Cursor and initialize it
With the repository cloned, open it in Cursor and initialize a Node.js project to create your package.json file.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Cursor and press Enter to open it.
- In Cursor, click File in the top menu bar.
- Click Open Folder.
- Select the pr-reviewer-action folder on your Desktop, then click Open.
Now open the integrated terminal inside Cursor.
- Press Ctrl+` (backtick key) to toggle the terminal panel, or click Terminal in the top menu bar and select New Terminal.
- Initialize your Node.js project by running this command:
npm init -y
What does this command do?
The npm init -y command creates a package.json file with default values. This file tracks your project's name, version, and dependencies.
The -y flag accepts all defaults so you do not have to answer prompts one by one.
- Confirm the file was created by running:
ls
You should see package.json listed in the output.
✔️ Awesome, I've got everything!
Great. Make sure you have saved your file and your terminal is showing pr-reviewer-action as the current directory.
ⓧ I'd like to double check the full code
Your package.json should look like this:
{
"name": "pr-reviewer-action",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Don't see package.json?
Make sure your terminal is inside the pr-reviewer-action folder. Run pwd to check your current directory.
If you are in a different folder, run cd ~/Desktop/pr-reviewer-action and try npm init -y again.
Still stuck? Help me initialize my Node.js project.
Your development environment is ready. Next up, you will define the action metadata and install the packages your GitHub Action needs.
Create the Action Metadata and Install Dependencies
Your development environment is ready. Now it's time to tell GitHub what your action is and how to run it.
Every custom GitHub Action needs a metadata file called action.yml that declares its inputs, outputs, and runtime. You also need the official Actions toolkit packages and a bundler to package your code into a single file GitHub can execute.
In this step, get ready to:
- Create the action.yml metadata file that defines your action's interface.
- Install the GitHub Actions toolkit and Rollup bundler.
- Configure your project for ES modules and create the bundler configuration.
Create the action metadata file
The action.yml file is the contract between your action and GitHub. It declares what inputs your action accepts, what outputs it produces, and which runtime to use.
- In the Cursor Explorer sidebar, right-click on the pr-reviewer-action folder and select New File.
- Name the file action.yml and press Enter.
- Paste the following content into action.yml:
name: 'PR Reviewer Auto-Assign'
description: 'Automatically assigns PR reviewers based on files changed'
inputs:
github-token:
description: 'GitHub token for API access'
required: true
config-path:
description: 'Path to reviewer config file'
required: false
default: '.github/reviewer-config.json'
outputs:
reviewers-assigned:
description: 'Comma-separated list of reviewers that were assigned'
runs:
using: 'node20'
main: 'dist/index.js'
What does this file do?
The inputs section declares two values your action needs. The github-token input is required so your action can authenticate with the GitHub API. The config-path input is optional and defaults to .github/reviewer-config.json.
The runs section tells GitHub to use the node20 runtime and look for your bundled code at dist/index.js. You'll create that bundled file later using Rollup.
- Save action.yml with Cmd+S (macOS) or Ctrl+S (Windows).
- Confirm the file appears in the Explorer sidebar at the root of your pr-reviewer-action folder.
Don't see action.yml in the sidebar?
Make sure you created the file at the root of pr-reviewer-action, not inside a subfolder. The file should sit at the same level as package.json.
Also check the file extension is .yml and not .yaml or .txt.
help me fix my action.yml file location
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
name: 'PR Reviewer Auto-Assign'
description: 'Automatically assigns PR reviewers based on files changed'
inputs:
github-token:
description: 'GitHub token for API access'
required: true
config-path:
description: 'Path to reviewer config file'
required: false
default: '.github/reviewer-config.json'
outputs:
reviewers-assigned:
description: 'Comma-separated list of reviewers that were assigned'
runs:
using: 'node20'
main: 'dist/index.js'
Install the toolkit and bundler packages
Your action needs the official GitHub Actions toolkit to interact with the workflow runner and the GitHub API. It also needs Rollup to bundle your source code into a single file that the node20 runtime can execute.
- Install the Actions toolkit packages by running this command in the Cursor integrated terminal:
npm install @actions/core@3.0.1 @actions/github@9.1.1
- Install the bundler as dev dependencies by running:
npm install --save-dev rollup@4.62.2 @rollup/plugin-commonjs@29.0.3 @rollup/plugin-node-resolve@16.0.3
Why two separate installs?
The toolkit packages (@actions/core and @actions/github) are runtime dependencies. They ship inside your bundled action. Rollup and its plugins are dev dependencies. They only run on your machine during the build step and never ship to GitHub.
Seeing permission or network errors?
If you see EACCES errors, make sure you're running the command inside the pr-reviewer-action folder where package.json lives. Run pwd to confirm your current directory.
If you see network timeouts, check your internet connection and try the command again.
help me fix npm install errors in my GitHub Action project
Now you need to configure your project for ES modules. The @actions/github package (v9+) is ESM-only, so your project must declare "type": "module" in package.json. You also need a build script that runs Rollup.
- Open package.json in Cursor.
- Replace the entire contents with the following:
{
"name": "pr-reviewer-auto-assign",
"version": "1.0.0",
"type": "module",
"description": "GitHub Action to auto-assign PR reviewers based on file paths",
"main": "dist/index.js",
"scripts": {
"build": "rollup --config rollup.config.js"
},
"dependencies": {
"@actions/core": "3.0.1",
"@actions/github": "9.1.1"
},
"devDependencies": {
"@rollup/plugin-commonjs": "29.0.3",
"@rollup/plugin-node-resolve": "16.0.3",
"rollup": "4.62.2"
}
}
What changed in package.json?
The "type": "module" field tells Node.js to treat all .js files as ES modules. This enables the import/export syntax that @actions/github v9 requires.
The "build" script lets you run npm run build to bundle your source code into dist/index.js. That's the file GitHub's runner will actually execute.
- Save package.json.
Seeing red underlines in package.json?
JSON is strict about formatting. Make sure there are no trailing commas after the last item in any object or array. Every key and string value must use double quotes.
help me fix JSON syntax errors in my package.json
✔️ Awesome, I've got everything!
Great. Make sure both package.json and action.yml are saved before continuing.
ⓧ I'd like to double check the full code
{
"name": "pr-reviewer-auto-assign",
"version": "1.0.0",
"type": "module",
"description": "GitHub Action to auto-assign PR reviewers based on file paths",
"main": "dist/index.js",
"scripts": {
"build": "rollup --config rollup.config.js"
},
"dependencies": {
"@actions/core": "3.0.1",
"@actions/github": "9.1.1"
},
"devDependencies": {
"@rollup/plugin-commonjs": "29.0.3",
"@rollup/plugin-node-resolve": "16.0.3",
"rollup": "4.62.2"
}
}
Create the Rollup configuration and source directory
Rollup needs a configuration file that tells it where to find your source code, where to output the bundle, and which plugins to use. You also need a src/ directory to hold your action's source code.
- Create a new file called rollup.config.js at the root of your pr-reviewer-action folder (same level as package.json).
- Paste the following content into rollup.config.js:
import commonjs from "@rollup/plugin-commonjs";
import nodeResolve from "@rollup/plugin-node-resolve";
export default {
input: "src/index.js",
output: {
file: "dist/index.js",
format: "es",
sourcemap: true,
},
plugins: [commonjs(), nodeResolve({ preferBuiltins: true })],
};
What does this configuration do?
The input field points to src/index.js where you'll write your action logic. The output field tells Rollup to create the bundled file at dist/index.js in ES module format.
The commonjs() plugin converts any CommonJS dependencies to ES modules. The nodeResolve() plugin bundles third-party packages from node_modules into your output file so the GitHub runner doesn't need to install them.
- Save rollup.config.js.
- Create the source directory by running this command in your terminal:
mkdir src
- Confirm the directory was created by running:
ls
- You should see src, node_modules, action.yml, package.json, and rollup.config.js in the output.
Missing src folder or other files?
If src doesn't appear, make sure your terminal is inside the pr-reviewer-action folder. Run pwd to check your current directory.
If node_modules is missing, the earlier npm install commands didn't complete successfully. Re-run them from the previous substep.
help me fix my project directory structure
✔️ Awesome, I've got everything!
Great. Make sure rollup.config.js is saved and your src/ directory exists.
ⓧ I'd like to double check the full code
import commonjs from "@rollup/plugin-commonjs";
import nodeResolve from "@rollup/plugin-node-resolve";
export default {
input: "src/index.js",
output: {
file: "dist/index.js",
format: "es",
sourcemap: true,
},
plugins: [commonjs(), nodeResolve({ preferBuiltins: true })],
};
Your action's foundation is in place. You have the metadata file that introduces your action to GitHub, all the packages you need, and a bundler ready to compile your code. Next up, you'll write the actual JavaScript logic that reads changed files and assigns reviewers.
Write the Reviewer Assignment Logic
Your action's metadata and dependencies are ready. Now you need the actual JavaScript that runs when a pull request is opened.
This step is where your action comes to life. You will write the logic that reads the PR event, fetches the changed files from the GitHub REST API, matches them against reviewer rules, and assigns the right people automatically.
In this step, get ready to:
- Create the action entry point with imports and input handling.
- Add file-matching logic that compares changed files to path rules.
- Add the reviewer assignment call that requests reviewers via the GitHub API.
Create the action entry point
Every GitHub Action needs a main script that GitHub's runner executes. Your action.yml already points to dist/index.js as the entry point, which Rollup will bundle from your source file.
- Create a new file called index.js inside the src/ folder in Cursor.
- Add the imports and the beginning of your run() function by pasting this code:
import * as core from "@actions/core";
import * as github from "@actions/github";
import { readFileSync } from "node:fs";
async function run() {
try {
// Read inputs defined in action.yml
const token = core.getInput("github-token", { required: true });
const configPath = core.getInput("config-path");
// Create an authenticated GitHub API client
const octokit = github.getOctokit(token);
const context = github.context;
// Skip if this isn't a pull request event
if (!context.payload.pull_request) {
core.info("This action only runs on pull request events. Skipping.");
return;
}
// Extract PR details from the event payload
const pullNumber = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
core.info(`Processing PR #${pullNumber} in ${owner}/${repo}`);
What does this code do?
This sets up the foundation of your action. Here is what each section handles:
- core.getInput() reads the inputs you defined in action.yml. The github-token lets your action authenticate with the GitHub API.
- github.getOctokit(token) creates an authenticated API client you will use to call GitHub endpoints.
- The if (!context.payload.pull_request) guard ensures the action exits gracefully if triggered by a non-PR event.
- pullNumber, owner, and repo are extracted from the event context so you can target the correct pull request in API calls.
You will not see a change yet. The next chunk adds the matching logic that gives this function its purpose.
Add the file-matching logic
Your action needs to know which files changed in the PR and compare them against the rules in your config. The GitHub REST API provides a listFiles endpoint that returns every file touched by a pull request.
- In src/index.js, add the following code directly below the core.info(`Processing PR...`) line:
// Read the reviewer configuration file
const configFile = readFileSync(configPath, "utf8");
const config = JSON.parse(configFile);
// Fetch the list of files changed in this PR
const { data: files } = await octokit.rest.pulls.listFiles({
owner,
repo,
pull_number: pullNumber,
});
const changedFiles = files.map((file) => file.filename);
core.info(`Changed files: ${changedFiles.join(", ")}`);
// Collect reviewers whose path rules match any changed file
const reviewersToAssign = new Set();
for (const rule of config.reviewers) {
for (const changedFile of changedFiles) {
const matches = rule.paths.some((path) => changedFile.startsWith(path));
if (matches) {
for (const user of rule.users) {
reviewersToAssign.add(user);
}
break;
}
}
}
What does this code do?
This is the core decision-making logic. It reads your config file, fetches the list of changed files, then checks each file against your path rules.
- readFileSync(configPath, "utf8") reads the JSON config file from the repository (checked out by the workflow).
- octokit.rest.pulls.listFiles() calls the GitHub API to get every file changed in this PR.
- The nested loop checks if any changed file's path starts with a rule's configured prefix (like frontend/ or api/).
- A Set is used to collect reviewer usernames because it automatically prevents duplicates if multiple rules match the same user.
You will not see a change yet. The next chunk completes the function by assigning the matched reviewers.
Add the reviewer assignment call
The final section handles a key edge case (you cannot review your own PR), then calls the GitHub API to actually request the matched reviewers.
- In src/index.js, add this code directly below the closing } of the outer for loop you just added:
// Remove the PR author (you can't review your own PR)
const prAuthor = context.payload.pull_request.user.login;
reviewersToAssign.delete(prAuthor);
if (reviewersToAssign.size === 0) {
core.info("No matching reviewers found for the changed files.");
return;
}
const reviewersList = Array.from(reviewersToAssign);
core.info(`Assigning reviewers: ${reviewersList.join(", ")}`);
// Call the GitHub API to request reviewers on the PR
await octokit.rest.pulls.requestReviewers({
owner,
repo,
pull_number: pullNumber,
reviewers: reviewersList,
});
core.setOutput("reviewers-assigned", reviewersList.join(","));
core.info("Reviewers assigned successfully!");
} catch (error) {
core.setFailed(error.message);
}
}
run();
What does this code do?
This section finishes the action by assigning reviewers and handling errors:
- reviewersToAssign.delete(prAuthor) removes the PR author from the reviewer set. GitHub does not allow you to request yourself as a reviewer.
- octokit.rest.pulls.requestReviewers() calls the GitHub API to assign the matched reviewers to the PR.
- core.setOutput() exposes the assigned reviewers as an action output that other workflow steps could use.
- The catch block uses core.setFailed() to mark the action as failed if anything throws an error.
- run() at the very end calls the async function so it actually executes when the runner loads the file.
- Save src/index.js.
- Confirm there are no red underlines in the editor. The file should have no syntax errors.
Seeing red underlines in src/index.js?
- Check that all three code chunks are in the correct order. The file should start with the import statements and end with run(); on its own line.
- Make sure the try { has a matching } catch (error) { and a final closing } for the function.
- Confirm you have "type": "module" in your package.json. Without it, the import statements will show as errors.
Still stuck? Help me fix syntax errors in my src/index.js file for a GitHub Action.
✔️ Awesome, I've got everything!
Great. Double check you have saved src/index.js and there are no errors showing.
ⓧ I'd like to double check the full code
Here is the complete src/index.js file. Compare yours against it:
import * as core from "@actions/core";
import * as github from "@actions/github";
import { readFileSync } from "node:fs";
async function run() {
try {
const token = core.getInput("github-token", { required: true });
const configPath = core.getInput("config-path");
const octokit = github.getOctokit(token);
const context = github.context;
if (!context.payload.pull_request) {
core.info("This action only runs on pull request events. Skipping.");
return;
}
const pullNumber = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
core.info(`Processing PR #${pullNumber} in ${owner}/${repo}`);
const configFile = readFileSync(configPath, "utf8");
const config = JSON.parse(configFile);
const { data: files } = await octokit.rest.pulls.listFiles({
owner,
repo,
pull_number: pullNumber,
});
const changedFiles = files.map((file) => file.filename);
core.info(`Changed files: ${changedFiles.join(", ")}`);
const reviewersToAssign = new Set();
for (const rule of config.reviewers) {
for (const changedFile of changedFiles) {
const matches = rule.paths.some((path) => changedFile.startsWith(path));
if (matches) {
for (const user of rule.users) {
reviewersToAssign.add(user);
}
break;
}
}
}
const prAuthor = context.payload.pull_request.user.login;
reviewersToAssign.delete(prAuthor);
if (reviewersToAssign.size === 0) {
core.info("No matching reviewers found for the changed files.");
return;
}
const reviewersList = Array.from(reviewersToAssign);
core.info(`Assigning reviewers: ${reviewersList.join(", ")}`);
await octokit.rest.pulls.requestReviewers({
owner,
repo,
pull_number: pullNumber,
reviewers: reviewersList,
});
core.setOutput("reviewers-assigned", reviewersList.join(","));
core.info("Reviewers assigned successfully!");
} catch (error) {
core.setFailed(error.message);
}
}
run();
Your reviewer assignment logic is complete. Next up, you will create the config file that defines your path-to-reviewer rules and the workflow that triggers your action on pull requests.
Create the Config File and Workflow
Your action's JavaScript logic is complete. It knows how to read a config file, match changed files against path rules, and request reviewers via the GitHub API.
Now you need two more pieces: the configuration file that tells your action which reviewers to assign for which file paths, and a workflow file that tells GitHub when to trigger your action. You'll also bundle everything and push it to your repository.
In this step, get ready to:
- Create the reviewer configuration file with path-to-reviewer rules.
- Create the GitHub Actions workflow that triggers on pull requests.
- Build the bundled output and push everything to GitHub.
Create the reviewer configuration file
Your action reads reviewer rules from a JSON file. Each rule maps a set of path prefixes to a list of GitHub usernames who should review changes in those paths.
- Create the directory structure your config and workflow files need by running these commands in the Cursor terminal:
mkdir -p .github/workflows
- Create a new file at .github/reviewer-config.json and add the following reviewer rules:
{
"reviewers": [
{
"paths": ["frontend/", "styles/", "public/"],
"users": ["frontend-reviewer-username"]
},
{
"paths": ["api/", "server/", "database/"],
"users": ["backend-reviewer-username"]
},
{
"paths": ["docs/", "README"],
"users": ["docs-reviewer-username"]
}
]
}
What does this config do?
Each object in the reviewers array is a rule. The paths array lists path prefixes to match against changed files. The users array lists the GitHub usernames to assign as reviewers when a file matches.
For example, if someone opens a PR that changes frontend/app.js, the action sees that it starts with frontend/ and assigns frontend-reviewer-username as a reviewer.
- Save .github/reviewer-config.json.
- Confirm the file exists by running:
ls .github/
You should see reviewer-config.json and workflows listed in the output.
Not seeing reviewer-config.json?
Make sure you created the file inside the .github directory, not in the project root. The path should be .github/reviewer-config.json.
Also check that you ran mkdir -p .github/workflows first to create the directory.
Help me find where my config file ended up
Create the GitHub Actions workflow
A GitHub Actions workflow is a YAML file that tells GitHub when and how to run your action. You need to define the trigger event, set permissions, and wire up the steps.
- Create a new file at .github/workflows/assign-reviewers.yml and add the following workflow definition:
name: Auto Assign Reviewers
on:
pull_request:
types: [opened, reopened]
permissions:
pull-requests: write
contents: read
jobs:
assign-reviewers:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Run reviewer assignment
uses: ./
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
What does this workflow do?
- on: pull_request: types: [opened, reopened] tells GitHub to run this workflow every time a pull request is opened or reopened.
- permissions grants the workflow the ability to write to pull requests (to assign reviewers) and read repository contents (to access the config file).
- actions/checkout@v6 checks out your repository code so that readFileSync in your action can find the config file on disk.
- uses: ./ runs your local action from the repository root. GitHub looks for action.yml in that directory.
- ${{ secrets.GITHUB_TOKEN }} is a token GitHub automatically provides to every workflow run. You do not need to create it manually.
- Save .github/workflows/assign-reviewers.yml.
- Confirm the file is in the right location by running:
ls .github/workflows/
You should see assign-reviewers.yml in the output.
Workflow file not showing up?
Double check that the file is inside .github/workflows/ (two levels deep from the project root). GitHub only recognizes workflow files in this exact directory.
Also make sure the file extension is .yml and not .yaml. Both work for GitHub, but the filename must match exactly what you saved.
Help me troubleshoot my workflow file location
Build and push to GitHub
Your action.yml points to dist/index.js as the entry point. That file does not exist yet. You need to run Rollup to bundle your source code and its dependencies into a single file that GitHub's runner can execute.
- Build the bundled action file by running:
npm run build
- Verify the bundled output was created by running:
ls dist/
You should see index.js and index.js.map in the output.
Why bundle into dist/?
GitHub Actions runners execute the file specified in action.yml directly. They do not run npm install for your action's dependencies. Bundling packs everything into one file so the runner can execute it without installing packages.
Build command failed?
Make sure your rollup.config.js exists in the project root and that you ran npm install in a previous step. Rollup needs its plugins installed to bundle correctly.
If you see an error about missing modules, run npm install again and then retry npm run build.
Help me fix my build error
- Commit all your files (including the dist/ folder) and push to GitHub by running these commands:
git add .
git commit -m "Add config, workflow, and bundled output"
git push
Why commit dist/ to the repository?
Normally you would add dist/ to your .gitignore. But GitHub Actions needs the bundled file to be in the repository because it runs dist/index.js directly from your repo without a build step.
- Open your repository on GitHub in your browser.
- Confirm you can see the .github folder, the dist folder, and action.yml in the file listing.
Push was rejected?
If git asks you to pull first, run git pull --rebase and then try git push again. This happens when the remote repository has changes you do not have locally.
Help me push my code to GitHub
✔️ Awesome, I've got everything!
Great. Double check that you have saved all your files and that your push completed without errors.
ⓧ I'd like to double check the full code
Here is the complete code for the two files you created in this step.
{
"reviewers": [
{
"paths": ["frontend/", "styles/", "public/"],
"users": ["frontend-reviewer-username"]
},
{
"paths": ["api/", "server/", "database/"],
"users": ["backend-reviewer-username"]
},
{
"paths": ["docs/", "README"],
"users": ["docs-reviewer-username"]
}
]
}
name: Auto Assign Reviewers
on:
pull_request:
types: [opened, reopened]
permissions:
pull-requests: write
contents: read
jobs:
assign-reviewers:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Run reviewer assignment
uses: ./
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
Your action is fully deployed to GitHub. Everything is in place: the config, the workflow trigger, and the bundled code. Next up, you will test it with a real pull request to see the reviewer assignment happen automatically.
Test with a Real Pull Request
Your action is built, bundled, and pushed to GitHub. The workflow is ready to trigger. Now it is time to prove that everything works end-to-end with a real pull request.
To test the reviewer assignment, you need at least one collaborator on your repository whose username appears in your config. The action removes the PR author from the reviewers list (you cannot review your own PR), so a second person is required.
In this step, get ready to:
- Add a collaborator to your repository and update the reviewer config.
- Create a test branch, add a file, and open a pull request.
- Verify the workflow ran and the reviewer was automatically assigned.
Add a collaborator to your repository
The GitHub REST API requires the reviewer to have collaborator access on the repository. Without it, the requestReviewers call will fail.
- Open your pr-reviewer-action repository on GitHub in your browser.
- Click Settings in the top navigation bar of your repository.
- In the left sidebar under Access, click Collaborators.
- Click Add people.
- Type your collaborator's GitHub username (a friend or a second account you own) and select them from the list.
- Click Add to send the invitation.
What happens next?
Your collaborator receives an email invitation. They must accept it before the action can assign them as a reviewer. Ask them to check their email or GitHub notifications and accept the invitation before you continue.
If you are using a second account you own, sign in to that account and accept the invitation from the notifications bell in the top-right corner.
Now update your reviewer config to include your collaborator's real GitHub username.
- In Cursor, open .github/reviewer-config.json.
- Find this line in the first rule:
"users": ["frontend-reviewer-username"]
- Replace frontend-reviewer-username with your collaborator's actual GitHub username, so the line looks like this:
"users": ["[[COLLABORATOR_USERNAME=\"your-collaborators-github-username\"]"]
- Save .github/reviewer-config.json.
- Commit and push the updated config by running these commands in your terminal:
git add .github/reviewer-config.json
git commit -m "Add collaborator username to reviewer config"
git push
Why push to main first?
When you create a new branch from main, it inherits all of main's files. By pushing the updated config to main first, your new test branch will have the correct reviewer username when the workflow checks it out.
Push rejected or authentication error?
Make sure you are on the main branch before pushing. Run git branch to check which branch you are on. If you see an authentication error, you may need to sign in to GitHub from the terminal using a personal access token or SSH key.
help me fix a git push authentication error
Create a test branch and open a pull request
Your workflow triggers on pull_request events with types opened and reopened. To fire it, you need a new branch with a changed file that matches one of your config rules.
- Create a new branch by running this command:
git checkout -b test-reviewer-action
- Create a test file inside a frontend/ directory by running these commands:
mkdir frontend
echo "// Test file for reviewer assignment" > frontend/app.js
- Commit and push the new branch by running:
git add frontend/app.js
git commit -m "Add frontend test file"
git push -u origin test-reviewer-action
What does this do?
The -u flag sets the upstream tracking branch on the remote so future pushes from this branch work without specifying the remote name. The file frontend/app.js lives inside the frontend/ path, which matches the first rule in your reviewer config.
Now open a pull request on GitHub.
- Go to your pr-reviewer-action repository on GitHub in your browser.
- You should see a banner saying your branch was recently pushed. Click Compare & pull request.
- Leave the title as-is and click Create pull request.
No banner showing?
If the banner is not visible, click the Pull requests tab, then click New pull request. Select test-reviewer-action as the compare branch and click Create pull request.
Verify the workflow and reviewer assignment
Opening the pull request fires the pull_request event, which triggers your GitHub Actions workflow.
- Click the Actions tab at the top of your repository.
- You should see a workflow run named Auto Assign Reviewers in progress or completed. Click on it.
- Click the assign-reviewers job to expand its logs.
- Expand the Run reviewer assignment step. You should see log messages including the changed files and the reviewer being assigned.
What should I see in the logs?
Your action prints several messages. You should see lines like:
- Processing PR #1 in your-username/pr-reviewer-action.
- Changed files: frontend/app.js.
- Assigning reviewers: your-collaborator (with the real username).
- Reviewers assigned successfully!.
- Navigate back to your pull request by clicking Pull requests and selecting your open PR.
- On the right sidebar under Reviewers, you should see your collaborator's username listed as a requested reviewer.
Workflow failed or no reviewer assigned?
Check these common causes:
- Your collaborator has not accepted the invitation yet. The API cannot request reviews from pending collaborators.
- The username in reviewer-config.json does not exactly match the collaborator's GitHub username (case-sensitive).
- The dist/index.js file was not rebuilt after your last code change. Run npm run build, commit, and push again.
help me debug why my GitHub Action workflow failed
Secret mission
Add Glob Patterns and Default Reviewers
Your action works, but it only matches exact path prefixes. Real codebases need flexible pattern matching. Can you add glob-style wildcards and a safety net that assigns default reviewers when no rules match?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally and uses a free GitHub account, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Local pr-reviewer-action project folder on your Desktop.
- GitHub repository pr-reviewer-action (including the workflow, action metadata, and test branches).
Keep everything running
No action needed. Choose this if you plan to keep iterating on your GitHub Action or want to reuse it in other repositories.
- Your local project folder stays on your Desktop.
- Your GitHub repository stays active and the workflow continues to trigger on new pull requests.
- Node.js remains installed on your machine for future JavaScript projects.
Pause - I'll come back to this later
Disable the workflow so it stops running on new PRs, but keep all your code intact for later.
- Go to your pr-reviewer-action repository on GitHub.
- Click the Actions tab.
- Select the Auto Assign Reviewers workflow from the left sidebar.
- Click the ... menu (three dots) and select Disable workflow.
- Your local files and repository remain untouched. Re-enable the workflow anytime by returning to the same menu.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow both steps below to clean up completely.
Delete the GitHub repository:
- Go to your pr-reviewer-action repository on GitHub.
- Click Settings (under the repository name).
- Scroll down to the Danger Zone section.
- Click Delete this repository.
- Click I want to delete this repository, then click I have read and understand these effects.
- Type the repository name to confirm, then click Delete this repository.
Delete the local project folder:
- Delete the local folder by running this command in Terminal (macOS/Linux):
rm -rf ~/Desktop/pr-reviewer-action
- On Windows, run this in PowerShell instead:
Remove-Item -Recurse -Force ~\Desktop\pr-reviewer-action
Nice Work!
Nice Work!
Nice work! You just built a custom DevOps automation from scratch and deployed it on a real GitHub repository.
You've learned how to:
- Build a custom GitHub Action from scratch using JavaScript, the Actions toolkit, and the node20 runtime with action.yml metadata.
- Use the GitHub REST API via Octokit to read pull request files and automatically assign reviewers based on file path rules.
- Bundle an ESM JavaScript project with Rollup and deploy a working automation triggered by a real pull request.
- Secret Mission: Added glob pattern matching with * and ** wildcards, plus a default reviewer fallback for files that match no rules.
Ready to quiz yourself?