Skip to content

Automated Pull Request Review

Automated code review system using GitHub Actions and Claude AI to provide comprehensive feedback on pull requests.


Overview

This system automatically reviews pull requests against our code review checklist, providing structured feedback with prioritized issues (P0/P1/P2) before code is merged to main.

What It Does

Automatic Static Analysis: - ✅ Runs ESLint for code quality - ✅ Runs TypeScript compiler for type safety - ✅ Captures lint and type errors

AI-Powered Code Review: - ✅ Reviews code changes against comprehensive checklist - ✅ Identifies security vulnerabilities - ✅ Catches React anti-patterns - ✅ Checks data integrity patterns - ✅ Reviews error handling - ✅ Assesses performance considerations

Structured Feedback: - ✅ Posts review as PR comment - ✅ Prioritizes issues (P0/P1/P2) - ✅ Provides specific, actionable recommendations - ✅ Updates comment on subsequent pushes


Workflow

Hybrid Approach

For small changes (continue current workflow):

git add -A && git commit -m "fix: typo" && git push  # Direct to main

For significant features (use PR workflow):

# 1. Create feature branch
git checkout -b feature/dashboard-redesign

# 2. Implement feature
# ... work, commit as normal ...

# 3. Push branch
git push -u origin feature/dashboard-redesign

# 4. Create PR
gh pr create --title "Dashboard redesign" --body "Complete redesign..."

# 5. Automated review runs
# - Static analysis (lint, types)
# - Claude AI review
# - Feedback posted to PR

# 6. Address P0 issues
# ... fix critical issues ...
git add -A && git commit -m "fix: address P0 review findings"
git push

# 7. Review updates automatically

# 8. Merge when ready
gh pr merge --squash


Setup

Prerequisites

1. Anthropic API Key

Obtain from: https://console.anthropic.com/

2. GitHub Secret Configuration

Add as organization or repository secret: - Name: ANTHROPIC_API_KEY - Value: Your Anthropic API key

To add:

# Via gh CLI
gh secret set ANTHROPIC_API_KEY

# Or via GitHub UI:
# Settings → Secrets and variables → Actions → New repository secret

Adding to a Project

1. Ensure tower-fleet workflow exists:

ls /root/tower-fleet/.github/workflows/pr-review.yml  # Should exist

2. Create workflow in your project:

.github/workflows/pr-review.yml:

name: PR Code Review

on:
  pull_request:
    types: [opened, synchronize, reopened]
    branches:
      - main

jobs:
  review:
    name: Automated Review
    uses: jakecelentano/tower-fleet/.github/workflows/pr-review.yml@main
    with:
      project_name: your-project-name  # e.g., home-portal
      node_version: '20'               # Optional, defaults to 20
    secrets:
      ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

3. Push workflow to repository:

git add .github/workflows/pr-review.yml
git commit -m "chore: add automated PR review workflow"
git push

4. Test with a PR:

git checkout -b test/automated-review
echo "# Test" >> README.md
git add -A && git commit -m "test: automated review"
git push -u origin test/automated-review
gh pr create --title "Test automated review" --body "Testing..."


Review Output

Example Review Comment

# 🤖 Automated Code Review

**Project**: home-portal
**PR**: #42 - Add dashboard widgets
**Author**: @jakecelentano

---

## ✅ Strengths

- Well-structured component architecture
- Proper TypeScript types throughout
- Good separation of concerns between UI and data fetching
- Comprehensive error handling

---

## 🚨 Critical Issues (P0)

**Must fix before merge**

1. **XSS Vulnerability in Widget Display** - User-generated widget titles rendered without sanitization
   - **Location**: `components/WidgetCard.tsx:45`
   - **Impact**: Potential XSS attack if user enters malicious HTML
   - **Fix**: React escapes by default, but if using `dangerouslySetInnerHTML`, sanitize with DOMPurify

2. **Missing Authentication Check** - API route missing auth validation
   - **Location**: `app/api/widgets/route.ts:12`
   - **Impact**: Unauthenticated users could create widgets
   - **Fix**: Add `const { data: { user } } = await supabase.auth.getUser()` check

---

## ⚠️ High Priority (P1)

**Should fix soon**

1. **Error Handling Gap** - Widget fetch failure not handled
   - **Location**: `app/dashboard/page.tsx:23`
   - **Impact**: UI breaks if API fails
   - **Fix**: Add try-catch and show error state

---

## 📋 Medium Priority (P2)

**Nice to have improvements**

1. **Performance Optimization** - Widget list not memoized
   - **Location**: `components/WidgetList.tsx:18`
   - **Impact**: Unnecessary re-renders on parent updates
   - **Fix**: Wrap in `useMemo(() => widgets.map(...), [widgets])`

---

## 📊 Static Analysis Details

### Linting
✅ Passed

### Type Checking
✅ Passed

---

## 🎯 Recommendation

🚫 **Changes requested** - P0 issues must be addressed

---

<sub>Generated by [Automated PR Review](https://github.com/jakecelentano/tower-fleet/blob/main/docs/workflows/automated-pr-review.md)</sub>

Priority Definitions

P0 - Critical (Release-Blocking)

These MUST be fixed before merge. The workflow will exit with error code 1 if P0 issues are detected.

Security: - SQL injection vulnerabilities - XSS vulnerabilities - Authentication bypass - Unvalidated file uploads - Exposed secrets or credentials - Missing auth checks on API routes

Type Safety: - TypeScript compilation errors - Unsafe type assertions without validation - Critical type mismatches

Data Integrity: - Missing database migrations for schema changes - Data loss scenarios - Broken foreign key relationships - Violates documented invariants (see App Invariants)

Critical Bugs: - Application crashes - Data corruption - Breaking changes without migration path

P1 - High Priority (Should Fix Soon)

Error Handling: - Missing error boundaries - Unhandled promise rejections - Silent failures

Performance: - N+1 query patterns - Memory leaks - Blocking operations on main thread

React Anti-Patterns: - Component creation during render - Incorrect hook usage - Missing cleanup in effects

P2 - Medium Priority (Nice to Have)

Code Quality: - Unused imports or variables - Inconsistent patterns - Missing documentation

Testing: - Missing test coverage - Edge cases not considered

Optimization: - Potential memoization opportunities - Bundle size concerns - Minor performance improvements


Static Analysis Patterns

Beyond ESLint and TypeScript, the review checks for specific dangerous patterns.

Forbidden Patterns (P0)

These patterns are always flagged as critical issues:

Pattern Risk Detection
String interpolation in SQL SQL Injection \SELECT.*\${` in code
dangerouslySetInnerHTML without sanitization XSS Code review
API route without auth check Auth bypass Missing auth.getUser() call
user_id column without NOT NULL Data orphaning Schema review
RLS not enabled on user data table Data leak Schema review
Hardcoded secrets/API keys Credential exposure Pattern matching

SQL Injection Detection

Forbidden:

// ❌ String interpolation in SQL
const query = `SELECT * FROM users WHERE id = '${userId}'`
await supabase.rpc('raw_query', { sql: query })

// ❌ String concatenation
const sql = "SELECT * FROM " + tableName

Required:

// ✅ Parameterized via Supabase client
const { data } = await supabase.from('users').select().eq('id', userId)

// ✅ RPC with parameters
await supabase.rpc('get_user', { user_id: userId })

Local check:

# Scan for string interpolation in SQL contexts
grep -rn "\`SELECT\|INSERT\|UPDATE\|DELETE.*\${" --include="*.ts" --include="*.tsx" app/ lib/

Authentication Check Detection

API routes must verify auth before data access:

// ✅ Required pattern
export async function GET(request: NextRequest) {
  const supabase = await createClient()

  // Auth check MUST come before any data operations
  const { data: { user }, error: authError } = await supabase.auth.getUser()
  if (authError || !user) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

  // Now safe to access data
  const { data } = await supabase.from('items').select()
  // ...
}

Local check:

# Find API routes without auth.getUser()
for f in $(find app/api -name "route.ts"); do
  if ! grep -q "auth.getUser" "$f"; then
    echo "⚠️  Missing auth check: $f"
  fi
done

RLS Enforcement Detection

Schema migrations must enable RLS on user data tables:

-- ✅ Required for any table with user_id
ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;

CREATE POLICY "users_own_data" ON my_table
  FOR ALL
  USING (auth.uid() = user_id);

Local check:

# Find tables created without RLS in migrations
grep -l "CREATE TABLE" supabase/migrations/*.sql | while read f; do
  tables=$(grep -oP "CREATE TABLE \K\w+" "$f")
  for table in $tables; do
    if ! grep -q "ENABLE ROW LEVEL SECURITY" "$f" | grep -q "$table"; then
      echo "⚠️  Table '$table' may be missing RLS in $f"
    fi
  done
done

Invariant Violation Detection

Cross-reference with documented invariants in app-invariants.md:

Schema checks:

# Check for user_id columns missing NOT NULL
grep -n "user_id UUID" supabase/migrations/*.sql | grep -v "NOT NULL"

# Check for missing CHECK constraints on enum-like columns
grep -n "TEXT.*CHECK" supabase/migrations/*.sql


Pre-Flight Checklist

Run these checks locally before creating a PR:

Quick Checks (< 1 minute)

# 1. Type safety
npx tsc --noEmit

# 2. Linting
npm run lint

# 3. Build succeeds
npm run build

Security Checks (< 2 minutes)

# 4. SQL injection patterns
grep -rn "\`SELECT\|INSERT\|UPDATE\|DELETE.*\${" --include="*.ts" --include="*.tsx" app/ lib/ && echo "❌ SQL injection risk" || echo "✅ No SQL injection patterns"

# 5. Auth checks on API routes
echo "Checking API routes for auth..."
for f in $(find app/api -name "route.ts" 2>/dev/null); do
  if ! grep -q "auth.getUser\|getServerSession" "$f"; then
    echo "⚠️  Missing auth: $f"
  fi
done

# 6. Hardcoded secrets
grep -rn "sk-\|password.*=.*['\"]" --include="*.ts" --include="*.tsx" . 2>/dev/null | grep -v node_modules | grep -v ".env" && echo "❌ Possible hardcoded secret" || echo "✅ No hardcoded secrets"

Schema Checks (if migrations changed)

# 7. RLS enabled
grep -l "CREATE TABLE" supabase/migrations/*.sql | xargs grep -L "ENABLE ROW LEVEL SECURITY" && echo "❌ Tables missing RLS" || echo "✅ RLS enabled"

# 8. user_id NOT NULL
grep -n "user_id UUID" supabase/migrations/*.sql | grep -v "NOT NULL" && echo "❌ user_id missing NOT NULL" || echo "✅ user_id constraints OK"

One-Liner Pre-Flight

# Run all checks
npx tsc --noEmit && npm run lint && npm run build && echo "✅ Pre-flight passed"

When to Use PRs

Use Pull Requests For:

Major features - New functionality, significant refactors ✅ Multi-file changes - Changes spanning multiple components ✅ Experimental work - Trying new patterns or approaches ✅ Database schema changes - Migrations, RLS policies ✅ Security-sensitive changes - Auth, permissions, data access ✅ API changes - New endpoints, breaking changes

Push Directly to Main For:

Bug fixes - Small, isolated fixes ✅ Documentation - README updates, comments ✅ Configuration - .env updates, package.json tweaks ✅ Styling - CSS adjustments, minor UI polish ✅ Typos - Content or code typos

When in doubt, use a PR. Better to get automated feedback than miss an issue.


Troubleshooting

Workflow Not Triggering

Check PR is targeting main:

gh pr view --json baseRefName
# Should show: "baseRefName": "main"

Verify workflow file:

cat .github/workflows/pr-review.yml

Check Actions tab in GitHub UI for errors

API Key Issues

Error: ANTHROPIC_API_KEY environment variable is not set

Solution:

# Check secret exists
gh secret list | grep ANTHROPIC

# Set if missing
gh secret set ANTHROPIC_API_KEY

Review Comment Not Posting

Check GitHub token permissions: - Settings → Actions → General → Workflow permissions - Should be: "Read and write permissions"

Manually check workflow logs:

gh run list --workflow=pr-review.yml
gh run view <run-id>

Claude API Rate Limits

Error: 429 Too Many Requests

Solution: - Wait and retry - Claude API has generous rate limits - For high-volume usage, consider batching or delays

Type/Lint Errors Not Shown

Workflow captures output but doesn't fail.

Check artifacts in GitHub Actions run: - Download "analysis-results" artifact - Contains full lint-output.txt and type-output.txt


Cost Considerations

Claude API Usage

Per review cost: - Input tokens: ~2,000-5,000 (diff + checklist) - Output tokens: ~1,000-2,000 (review) - Cost: $0.003-0.015 per review

Monthly estimate (varies by PR frequency): - 10 PRs/month: ~$0.10 - 50 PRs/month: ~$0.50 - 100 PRs/month: ~$1.00

Very cost-effective compared to manual review time.

GitHub Actions

Free tier: - 2,000 minutes/month for private repos - Unlimited for public repos

Per workflow run: - ~3-5 minutes (install deps, analysis, API call)

Monthly usage (assuming 50 PRs with 2 updates each): - 50 PRs × 2 updates × 5 minutes = 500 minutes - Well within free tier


Customization

Adjust Node Version

with:
  project_name: home-portal
  node_version: '18'  # Override default

Disable for Specific PRs

Add label skip-review to PR (requires label check in workflow).

Customize Review Checklist

Edit: /root/tower-fleet/.github/scripts/claude-review.js

Search for "Code Review Checklist" section and modify.


Architecture

Components

1. Reusable Workflow (tower-fleet/.github/workflows/pr-review.yml) - Central workflow definition - Called by project workflows - Handles orchestration

2. Review Script (tower-fleet/.github/scripts/claude-review.js) - Node.js script - Calls Anthropic API - Formats review output

3. Project Workflows (.github/workflows/pr-review.yml in each project) - Triggers on PR events - Passes project-specific config - References reusable workflow

Data Flow

PR opened/updated
Project workflow triggered
Calls reusable workflow (tower-fleet)
Static Analysis Job:
  - npm ci
  - npm run lint → lint-output.txt
  - npx tsc --noEmit → type-output.txt
  - git diff → pr-diff.txt
  - git log → pr-commits.txt
Claude Review Job:
  - Downloads artifacts
  - Runs claude-review.js
  - Calls Anthropic API
  - Generates review-output.md
Post Comment Job:
  - Posts/updates PR comment with review

File Structure

For Private Repositories (Current Standard):

tower-fleet/
├── .github/
│   ├── workflows/
│   │   └── pr-review.yml           # Source of truth (reference only)
│   └── scripts/
│       ├── package.json            # Source of truth for scripts
│       └── claude-review.js        # Source of truth for scripts
└── docs/
    └── workflows/
        └── automated-pr-review.md  # This file

project/
├── .github/
│   ├── workflows/
│   │   └── pr-review.yml           # Full embedded workflow (copy)
│   └── scripts/
│       ├── package.json            # Copied from tower-fleet
│       └── claude-review.js        # Copied from tower-fleet

Note: Since our repos are private, reusable workflows don't work. Each repo must embed the complete workflow and scripts.


Private Repository Setup

Why Embed Instead of Reuse?

GitHub Actions reusable workflows require public repositories or specific org-level permissions for private repos. Since tower-fleet and our project repos are private, we must embed the workflow and scripts directly in each repository.

Standard Setup Process

1. Copy Workflow File

Create .github/workflows/pr-review.yml in your project:

cp /root/tower-fleet/.github/workflows/pr-review.yml \
   /root/projects/your-project/.github/workflows/pr-review.yml

Edit the file to: - Remove workflow_call trigger (change to pull_request) - Remove inputs section - Hardcode project_name in environment variables - Update paths from tower-fleet/.github/scripts/ to .github/scripts/

2. Copy Scripts

mkdir -p /root/projects/your-project/.github/scripts
cp /root/tower-fleet/.github/scripts/claude-review.js \
   /root/projects/your-project/.github/scripts/
cp /root/tower-fleet/.github/scripts/package.json \
   /root/projects/your-project/.github/scripts/

3. Commit and Test

cd /root/projects/your-project
git add .github/
git commit -m "chore: add embedded PR review workflow"
git push

# Create test PR
git checkout -b test/pr-review
echo "test" >> README.md
git add -A && git commit -m "test: PR review workflow"
git push -u origin test/pr-review
gh pr create --fill

Keeping Scripts Up to Date

⚠️ IMPORTANT: When updating scripts in tower-fleet, you must update all project repos.

Scripts are in tower-fleet (source of truth): - /root/tower-fleet/.github/scripts/claude-review.js - /root/tower-fleet/.github/scripts/package.json

Update all repos with:

/root/tower-fleet/scripts/sync-pr-review-scripts.sh

This script copies updated scripts to all project repositories.

Deployment Script

Use the deployment script to add PR review to new projects:

/root/tower-fleet/scripts/add-pr-review-to-repos.sh

This handles: - Creating .github/workflows/pr-review.yml with embedded workflow - Copying scripts to .github/scripts/ - Committing and pushing changes


Future Enhancements

Potential Improvements

Blocking on P0: - Currently non-blocking (hybrid workflow) - Could add: required: true in branch protection - Would prevent merge until P0s fixed

Custom Rules by Project: - Pass additional rules via workflow input - Project-specific security patterns - Framework-specific checks

Performance Benchmarks: - Track bundle size changes - Lighthouse scores on preview deployments - Performance regression detection

Test Coverage: - Integrate with test runners - Coverage diff on PR - Require coverage thresholds



Last Updated: 2025-12-17 Status: Active (pilot: home-portal)