A GitHub Actions bot that automatically reviews pull requests using the Claude API. When a PR is opened or updated, the bot fetches the diff, sends each changed file to Claude for analysis, and posts inline comments and a summary directly on the PR.
The bot is based on the following guide: cloudflare tutorials
A demo is available in the following pull request: demo
Copy .github/workflows/code-review.yml into your target repository.
Copy the entire scripts/ folder into the root of your target repository.
In your repository go to Settings → Secrets and variables → Actions and add:
| Secret name | Value |
|---|---|
ANTHROPIC_API_KEY |
Your API key from console.anthropic.com |
GITHUB_TOKEN is provided automatically by GitHub Actions, you do not need to add it.
Open a pull request and the bot will trigger automatically.
- Only files changed in the PR
- On a re-push to an existing PR, only files changed in that specific push (not the whole PR again)
package-lock.jsonand all*.lockfiles- Anything inside a
dist/directory
To add more exclusions, edit the IGNORED_FILES regex in scripts/src/constants.ts.
The prompt that controls what Claude focuses on lives in scripts/src/constants.ts as REVIEW_INSTRUCTIONS. Edit it to change the tone, focus areas, or output format.
GitHub Actions triggers the workflow in .github/workflows/code-review.yml whenever a PR is opened or a new commit is pushed to one. The workflow checks out the code, installs dependencies, and runs:
npm start → tsx src/review.ts
This is where execution begins. main() calls the other modules in order:
getPRFiles()— fetch the files to reviewreviewFile()— send each file to ClaudepostReview()— post inline comments on the PRpostSummary()— post a top-level summary comment
Handles all communication with GitHub:
getPRFiles()— decides which files to fetch. On a new PR (opened) it fetches all changed files vialistFiles. On a re-push (synchronize) it usescompareCommitsbetween the previous and new SHA so only the files touched in that push are reviewed. Both paths filter out ignored files and parse each patch into aDiffFile.parseValidLines()— reads the raw unified diff and builds aSet<number>of line numbers that actually exist in the new version of the file. Used to drop any comment Claude returns that references a line outside the diff.postReview()— posts all inline comments as a single GitHub review. If the batch call fails (e.g. one bad line number slipped through), it falls back to posting each comment individually so the rest are not lost.postSummary()— posts a top-level PR comment with a count of issues found per file, or a "no issues found" message if Claude had nothing to say.
Contains a single function reviewFile() which:
- Sends the file path and its diff patch to
claude-sonnet-4-6with the review instructions as a cached system prompt - Strips any markdown fences from the response (Claude sometimes wraps JSON in fences despite being told not to)
- Parses the JSON array of comments
- Filters out any comment whose
pathdoesn't match the file being reviewed or whoselineis not in the valid line set
Holds three exports used across the other modules:
REVIEW_INSTRUCTIONS— the system prompt sent to Claude on every callIGNORED_FILES— the regex used to filter out generated and lock filesMODEL— the Claude model name, in one place so it's easy to change
Defines two interfaces used across all modules:
DiffFile— a changed file with its filename, raw patch string, and the set of valid line numbersReviewComment— a single comment with a file path, line number, and body text