Automated PR Reviews with Cursor AI and GitHub Actions
Set up an AI-powered code review bot using Cursor CLI in GitHub Actions. Get thorough, context-aware feedback on every pull request automatically.
Manual code reviews are time-consuming and inconsistent. What if you could have an AI reviewer that checks every PR for security issues, performance problems, and code quality β automatically? This guide shows you how to set up Cursor AI as your automated PR reviewer using GitHub Actions.
What Youβll Build
A GitHub Actions workflow that:
- Automatically reviews PRs when opened
- Can be triggered manually with
/reviewcomment - Checks for security vulnerabilities, performance issues, and code quality
- Posts inline comments on specific lines
- Optionally blocks PRs with critical issues
Prerequisites
- A GitHub repository
- Cursor API key (get it from cursor.com)
- Basic understanding of GitHub Actions
π Official Docs: Cursor CLI GitHub Actions Integration
How It Works
PR Opened / /review comment
β
GitHub Actions triggered
β
Checkout code + Install Cursor CLI
β
Cursor Agent analyzes diff
β
Posts inline comments via GitHub API
β
(Optional) Blocks PR if critical issues found
The Workflow File
Create .github/workflows/cursor_pr_review.yml:
name: cursor-pr-review
on:
pull_request:
types: [opened, ready_for_review]
issue_comment:
types: [created]
permissions:
pull-requests: write
contents: read
issues: write
Triggers Explained
| Event | When it fires |
|---|---|
pull_request: opened | New PR is created |
pull_request: ready_for_review | Draft PR marked as ready |
issue_comment: created | Someone comments on PR |
The issue_comment trigger allows manual reviews via /review command.
Step-by-Step Breakdown
1. Job Conditions
jobs:
code-review:
if: >
(
github.event_name == 'pull_request' &&
github.event.action == 'opened'
) ||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '/review') &&
(
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'
)
)
timeout-minutes: 15
runs-on: ubuntu-latest
This job runs when:
- A PR is opened, OR
- Someone with repo access comments
/review
The author association check prevents random users from triggering expensive AI reviews.
2. Get PR Details
- name: Get PR details
id: pr-vars
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Get PR number based on event type
if [ "${{ github.event_name }}" == "issue_comment" ]; then
PR_URL="${{ github.event.issue.pull_request.url }}"
PR_NUMBER=$(echo "$PR_URL" | sed 's/.*\/pulls\///')
else
PR_NUMBER=${{ github.event.pull_request.number }}
fi
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
# Extract additional instructions from comment
ADDITIONAL_INSTRUCTIONS=""
if [ "${{ github.event_name }}" == "issue_comment" ]; then
COMMENT_BODY="${{ github.event.comment.body }}"
ADDITIONAL_INSTRUCTIONS=$(echo "$COMMENT_BODY" | sed "s|.*/review||" | xargs)
fi
Whatβs happening:
- Extracts PR number from different event types
- Parses additional instructions from
/review focus on securitystyle comments - Gets head and base SHA for diff comparison
3. Checkout & Install Cursor
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.pr-vars.outputs.pr_head_sha }}
- name: Install Cursor CLI
run: |
curl https://cursor.com/install -fsS | bash
echo "$HOME/.cursor/bin" >> $GITHUB_PATH
Key points:
fetch-depth: 0β Full git history for accurate diffsref: pr_head_shaβ Checkout the PR branch, not main- Cursor CLI is installed fresh each run
4. Get Changed Files
- name: Get additional PR details
id: pr-details
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER="${{ steps.pr-vars.outputs.pr_number }}"
# Get PR metadata
PR_DATA=$(gh pr view "$PR_NUMBER" --json title,body,additions,deletions,changedFiles)
# Get changed files
CHANGED_FILES=$(gh pr diff "$PR_NUMBER" --name-only)
Uses GitHub CLI (gh) to fetch:
- PR title and description
- Number of additions/deletions
- List of changed files
5. The AI Review (The Magic Part) πͺ
- name: Perform automated code review
env:
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
MODEL: opus-4.5-thinking
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BLOCKING_REVIEW: ${{ vars.BLOCKING_REVIEW || 'false' }}
run: |
agent --force --model "$MODEL" --output-format=text --print <<PROMPT_EOF
You are a senior software engineer performing a comprehensive code review...
PROMPT_EOF
The agent command runs Cursorβs AI with a detailed prompt that instructs it to:
Analyze multiple dimensions:
| Category | What it checks |
|---|---|
| π¨ Critical | Null dereferences, resource leaks, data corruption |
| π Security | SQL injection, XSS, hardcoded secrets, auth bypasses |
| β‘ Performance | N+1 queries, missing indexes, blocking I/O |
| ποΈ Architecture | Coupling, SRP violations, API design |
| π Code Quality | Naming, dead code, DRY violations |
| π§ͺ Testing | Missing tests, edge cases, assertions |
Comment format:
π **SQL Injection vulnerability**
The `user_id` is being concatenated directly into the query string.
This opens up the endpoint to SQL injection attacks.
Use parameterized queries instead:
cursor.execute('SELECT * FROM users WHERE id = %s', (user_id,))
6. Blocking Review (Optional)
- name: Check blocking review results
if: env.BLOCKING_REVIEW == 'true'
run: |
if [ "${CRITICAL_ISSUES_FOUND:-false}" = "true" ]; then
echo "β Critical issues found. Failing the workflow."
exit 1
fi
When BLOCKING_REVIEW is enabled:
- Critical (π¨) or security (π) issues will fail the workflow
- Can be used with branch protection to block merging
- Set via repository variable:
vars.BLOCKING_REVIEW
Security: Restricting Agent Permissions
Create .cursor/cli.json in your repo root:
{
"permissions": {
"allow": [],
"deny": ["Shell(git push)", "Shell(gh pr create)", "Write(**)"]
}
}
Why this matters:
| Denied Permission | Reason |
|---|---|
Shell(git push) | Prevent agent from pushing code |
Shell(gh pr create) | Prevent creating new PRs |
Write(**) | Prevent modifying any files |
The agent should only read and comment β never modify code.
Required Secrets & Variables
Secrets (Settings β Secrets β Actions)
| Name | Description |
|---|---|
CURSOR_API_KEY | Your Cursor API key |
GITHUB_TOKENis automatically provided by GitHub Actions.
Variables (Settings β Variables β Actions)
| Name | Default | Description |
|---|---|---|
BLOCKING_REVIEW | false | Set to true to fail on critical issues |
Usage
Automatic Review
Just open a PR. The workflow triggers automatically.
Manual Review
Comment on any PR:
/review
With specific focus:
/review focus on security and error handling
/review check for memory leaks
The additional instructions are passed to the AI for targeted review.
Example Output
When the workflow runs, youβll see:
Inline comments on specific lines:
π Potential XSS vulnerability
The
usernameis inserted directly into innerHTML without sanitization. UsetextContentinstead, or sanitize with DOMPurify.
Summary comment:
π PR Overview
This PR adds user search functionality to the dashboard. New
SearchBarcomponent with debounced API calls, results displayed inUserList.π‘ Suggestions
- Add loading state for better UX
- Consider caching search results
Cost Considerations
- Each review consumes API tokens based on diff size
opus-4.5-thinkingis powerful but expensive- For smaller projects, consider
claude-sonnetorgpt-4o - Use
/reviewmanual trigger to control costs
Tips for Better Reviews
- Write good PR descriptions β AI uses them for context
- Keep PRs small β Better analysis, lower cost
- Use conventional commits β Helps AI understand intent
- Configure
.cursor/rulesβ Add project-specific guidelines - Read the docs β Check Cursor CLI documentation for more examples
Troubleshooting
Review not triggering?
- Check if user has correct association (OWNER/MEMBER/COLLABORATOR)
- Verify
CURSOR_API_KEYis set correctly - Check workflow permissions in repo settings
Comments not appearing?
- Ensure
pull-requests: writepermission is set - Check GitHub Actions logs for API errors
Taking too long?
- Increase
timeout-minutes(default: 15) - Use a faster model
- Review smaller PRs
Full Workflow File
Hereβs the complete workflow for copy-paste.
name: cursor-pr-review
on:
pull_request:
types: [opened, ready_for_review]
issue_comment:
types: [created]
permissions:
pull-requests: write
contents: read
issues: write
jobs:
code-review:
if: >
(
github.event_name == 'pull_request' &&
github.event.action == 'opened'
) ||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '/review') &&
(
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'
)
)
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- name: Get PR details
id: pr-vars
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Get PR number based on event type
if [ "${{ github.event_name }}" == "issue_comment" ]; then
PR_URL="${{ github.event.issue.pull_request.url }}"
PR_NUMBER=$(echo "$PR_URL" | sed 's/.*\/pulls\///')
else
PR_NUMBER=${{ github.event.pull_request.number }}
fi
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
# Extract additional instructions from comment (if triggered by comment)
ADDITIONAL_INSTRUCTIONS=""
if [ "${{ github.event_name }}" == "issue_comment" ]; then
COMMENT_BODY="${{ github.event.comment.body }}"
ADDITIONAL_INSTRUCTIONS=$(echo "$COMMENT_BODY" | sed "s|.*/review||" | xargs)
if [ -n "$ADDITIONAL_INSTRUCTIONS" ]; then
echo "additional_instructions<<EOF" >> "$GITHUB_OUTPUT"
echo "$ADDITIONAL_INSTRUCTIONS" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
fi
fi
# Get PR details using GitHub API (works before checkout)
if [ "${{ github.event_name }}" == "pull_request" ]; then
PR_HEAD_SHA="${{ github.event.pull_request.head.sha }}"
PR_BASE_SHA="${{ github.event.pull_request.base.sha }}"
else
PR_DATA=$(curl -s -H "Authorization: token $GH_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER")
PR_HEAD_SHA=$(echo "$PR_DATA" | jq -r '.head.sha')
PR_BASE_SHA=$(echo "$PR_DATA" | jq -r '.base.sha')
fi
echo "pr_head_sha=$PR_HEAD_SHA" >> "$GITHUB_OUTPUT"
echo "pr_base_sha=$PR_BASE_SHA" >> "$GITHUB_OUTPUT"
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.pr-vars.outputs.pr_head_sha }}
- name: Install Cursor CLI
run: |
curl https://cursor.com/install -fsS | bash
echo "$HOME/.cursor/bin" >> $GITHUB_PATH
- name: Configure git identity
run: |
git config user.name "Cursor Agent"
git config user.email "cursoragent@cursor.com"
- name: Get additional PR details
id: pr-details
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER="${{ steps.pr-vars.outputs.pr_number }}"
# Get PR details using gh CLI (now that we have git repo)
PR_DATA=$(gh pr view "$PR_NUMBER" --json title,body,additions,deletions,changedFiles,baseRefName,headRefName)
echo "pr_data<<EOF" >> "$GITHUB_OUTPUT"
echo "$PR_DATA" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
# Get changed files
CHANGED_FILES=$(gh pr diff "$PR_NUMBER" --name-only)
echo "changed_files<<EOF" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
- name: Perform automated code review
env:
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
MODEL: opus-4.5-thinking
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BLOCKING_REVIEW: ${{ vars.BLOCKING_REVIEW || 'false' }}
ADDITIONAL_INSTRUCTIONS: ${{ steps.pr-vars.outputs.additional_instructions }}
run: |
ADDITIONAL_CONTEXT=""
if [ -n "$ADDITIONAL_INSTRUCTIONS" ]; then
ADDITIONAL_CONTEXT="- Additional Instructions: $ADDITIONAL_INSTRUCTIONS"
fi
agent --force --model "$MODEL" --output-format=text --print <<PROMPT_EOF
You are a senior software engineer performing a comprehensive code review in a GitHub Actions runner. The gh CLI is authenticated via GH_TOKEN. Your goal is to provide high-quality, constructive feedback that helps improve the codebase and mentor the author.
Context:
- Repo: ${{ github.repository }}
- PR Number: ${{ steps.pr-vars.outputs.pr_number }}
- PR Head SHA: ${{ steps.pr-vars.outputs.pr_head_sha }}
- PR Base SHA: ${{ steps.pr-vars.outputs.pr_base_sha }}
- Blocking Review: ${{ env.BLOCKING_REVIEW }}
${ADDITIONAL_CONTEXT}
Objectives:
1) Re-check existing review comments and reply resolved when addressed.
2) Review the current PR diff comprehensively across multiple dimensions.
3) Provide constructive, actionable feedback with context and suggestions.
4) Help the author learn and improve through explanatory comments.
Information Gathering:
- Get PR metadata: gh pr view --json title,body,labels,milestone,closingIssuesReferences,commits,comments,author
- Get commit messages: gh pr view --json commits --jq '.commits[].messageHeadline'
- Get diff: gh pr diff
- Get changed files with patches: gh api repos/${{ github.repository }}/pulls/${{ steps.pr-vars.outputs.pr_number }}/files --paginate --jq '.[] | {filename,patch,status,additions,deletions}'
- Understand the purpose from PR title, body, linked issues, and commit messages
- Read relevant source files to understand the broader context around the changes
${ADDITIONAL_INSTRUCTIONS:+$'\n - ADDITIONAL INSTRUCTIONS FROM REVIEWER (if provided): '${ADDITIONAL_INSTRUCTIONS}}
Analysis Dimensions (BE STRICT on security, performance, architecture, code quality):
π¨ Critical Issues (MUST fix):
- Null/undefined dereferences
- Resource leaks (unclosed files, connections, streams)
- SQL/NoSQL injection vulnerabilities
- XSS and other injection attacks
- Authentication/authorization bypasses
- Hardcoded secrets or credentials
- Race conditions and deadlocks
- Data corruption risks
- Unhandled exceptions in critical paths
π Security & Privacy:
- Input validation and sanitization
- Proper encoding/escaping of outputs
- Secure handling of sensitive data (PII, credentials)
- CORS and CSRF protection
- Secure defaults and fail-safe designs
- Proper use of cryptographic functions
- Audit logging for sensitive operations
β‘ Performance:
- N+1 query patterns
- Missing indexes for frequent queries
- Unnecessary allocations in hot paths
- Blocking I/O in async contexts
- Missing pagination for large datasets
- Inefficient algorithms (O(nΒ²) where O(n) is possible)
- Memory leaks and unbounded growth
- Missing caching opportunities
ποΈ Architecture & Design:
- Single Responsibility Principle violations
- Tight coupling between modules
- Proper abstraction levels
- Consistent patterns with existing codebase
- Appropriate use of design patterns
- API design and backward compatibility
- Database schema design and migrations
π Code Quality & Maintainability:
- Code readability and clarity
- Meaningful variable/function naming
- Dead code or unreachable paths
- Magic numbers without constants
- Overly complex logic that could be simplified
- DRY violations (duplicated code)
- Proper separation of concerns
π§ͺ Testing:
- Missing unit tests for new logic
- Missing edge case coverage
- Test assertions that actually verify behavior
- Mocking best practices
- Integration test considerations
- Suggest specific test scenarios if tests are missing
π Documentation:
- Missing or outdated comments for complex logic
- API documentation (JSDoc, docstrings, etc.)
- README updates if applicable
- Inline comments explaining "why" not "what"
βΏ Accessibility & Internationalization (for UI code):
- ARIA labels and semantic HTML
- Keyboard navigation support
- Color contrast and visual accessibility
- Hardcoded strings that should be localized
Review Procedure:
- Compute exact inline anchors for each issue (file path + diff position)
- Comments MUST be placed inline on the changed line in the diff
- Detect prior "no issues" comments (match: "β
no issues", "No issues found", "LGTM")
- If CURRENT run finds issues and prior "no issues" comments exist:
- Delete via: gh api -X DELETE repos/${{ github.repository }}/issues/comments/<comment_id>
- Or for PR comments: gh api -X DELETE repos/${{ github.repository }}/pulls/${{ steps.pr-vars.outputs.pr_number }}/comments/<comment_id>
- If deletion fails, edit to prefix "[Superseded by new findings]"
- If a previously reported issue appears fixed, reply: β
This issue appears to be resolved
- Avoid duplicates: skip if similar feedback exists on nearby lines
Commenting Guidelines:
- Max 15 inline comments total; prioritize by impact (security > performance > architecture > quality)
- One issue per comment; place on the exact changed line
- Write naturally like a human reviewer - explain the issue, why it matters, and how to fix
- Provide enough context so the author understands the root cause
- Include code example for the fix when applicable
- For subjective suggestions, phrase as "Consider..." or "You might want to..."
- Use emojis to categorize:
π¨ Critical (blocks merge)
π Security concern
β‘ Performance improvement
β οΈ Potential bug or logic issue
ποΈ Architecture/design suggestion
π Readability/maintainability
π§ͺ Testing suggestion
π‘ Tip or best practice
β
Resolved
Comment Format Example:
"π **SQL Injection vulnerability**
The \`user_id\` is being concatenated directly into the query string here. This opens up the endpoint to SQL injection - an attacker could pass something like \`1; DROP TABLE users--\` and it would execute.
Use parameterized queries instead:
\`\`\`python
cursor.execute('SELECT * FROM users WHERE id = %s', (user_id,))
\`\`\`"
Summary Comment Guidelines:
- Start with "# π PR Overview" header
- Write 2-4 sentences explaining what this PR does based on CODE DIFF (not just PR description)
- Focus on: what changed, why, and key files/functions modified
- Only add "## π₯ Breaking Changes" section if there are actual breaking changes:
- API contract changes (request/response schema, endpoints)
- Database schema changes (migrations needed)
- Environment variables added/removed/renamed
- Configuration format changes
- Only add "## π Impact" section if there's significant impact:
- Downstream services that need updates
- Data migrations required
- Rollback considerations
- Only add "## π‘ Suggestions" section for HIGH-PRIORITY suggestions NOT already in inline comments:
- Missing unit tests for critical logic
- Potential bugs or edge cases to handle
- Security hardening recommendations
- Performance improvements for hot paths
- Error handling gaps
- Do NOT duplicate inline comments in suggestions - if already commented inline, skip it
- Example:
"# π PR Overview
This PR adds JWT authentication to replace session-based auth. New \`auth/jwt.py\` handles token generation and validation, \`routes/login.py\` now returns tokens instead of cookies.
## π₯ Breaking Changes:
- \`POST /login\` response changed to \`{access_token, refresh_token}\`
- New required env: \`JWT_SECRET\`
## π‘ Suggestions
- Add unit tests for token expiration and refresh logic
- Consider rate limiting on token refresh endpoint"
Submission:
- If NO issues and NO prior comment: submit brief positive summary
- If issues exist and prior "no issues" comment exists: clean it up first
- Submit ONE review with inline comments + summary via GitHub Reviews API:
- For single-line comment: { "path": "<file>", "line": <line_number>, "side": "RIGHT", "body": "..." }
- For multi-line comment (when issue spans multiple lines): { "path": "<file>", "start_line": <start>, "line": <end>, "side": "RIGHT", "body": "..." }
- Use multi-line comments when: function/block spans lines, related changes are grouped, or context helps understanding
- "side": "RIGHT" for new code (additions), "LEFT" for old code (deletions)
- Submit: gh api repos/${{ github.repository }}/pulls/${{ steps.pr-vars.outputs.pr_number }}/reviews -f event=COMMENT -f body="\$SUMMARY" -f comments='[\$COMMENTS_JSON]'
- Do NOT use: gh pr review --approve or --request-changes
Blocking Behavior:
- If BLOCKING_REVIEW is true and any π¨ or π issues posted: echo "CRITICAL_ISSUES_FOUND=true" >> \$GITHUB_ENV
- Otherwise: echo "CRITICAL_ISSUES_FOUND=false" >> \$GITHUB_ENV
- Always set CRITICAL_ISSUES_FOUND at the end
PROMPT_EOF
- name: Check blocking review results
if: env.BLOCKING_REVIEW == 'true'
run: |
echo "Checking for critical issues..."
echo "CRITICAL_ISSUES_FOUND: ${CRITICAL_ISSUES_FOUND:-unset}"
if [ "${CRITICAL_ISSUES_FOUND:-false}" = "true" ]; then
echo "β Critical issues found and blocking review is enabled. Failing the workflow."
exit 1
else
echo "β
No blocking issues found."
fi
Thatβs it! You now have an AI code reviewer that never gets tired, never misses obvious issues, and provides consistent feedback on every PR. β¨