GitHub Code Review Automation Best Practices

GitHub Code Review Automation Best Practices

The Goal: Fast, Useful Feedback

Code Review Automation : Automated systems that analyze pull requests for issues including bugs, security vulnerabilities, style violations, and test coverage—providing feedback before human reviewers engage.

Automation should:

  • Run in under 5 minutes for most PRs
  • Catch real issues, not noise
  • Provide actionable feedback
  • Not block urgent changes unnecessarily

Layer 1: Branch Protection

Start here. This is built into GitHub.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Settings → Branches → Branch protection rules
branch_protection:
  required_status_checks:
    strict: true
    contexts:
      - "lint"
      - "test"
      - "security"

  required_pull_request_reviews:
    required_approving_review_count: 1
    dismiss_stale_reviews: true
    require_code_owner_reviews: true

  restrictions:
    users: []
    teams: ["maintainers"]

CODEOWNERS for Automatic Assignment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# .github/CODEOWNERS

# Default owners for everything
*       @your-org/core-team

# Security-sensitive files need security review
/auth/      @your-org/security
/api/       @your-org/security
*.env*      @your-org/security

# Frontend changes
/src/components/    @your-org/frontend
/src/styles/        @your-org/frontend

# Infrastructure
/terraform/     @your-org/platform
/k8s/           @your-org/platform
Dockerfile      @your-org/platform

Layer 2: Basic Checks

Fast, deterministic checks that run on every PR.

Linting and Formatting

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# .github/workflows/lint.yml
name: Lint

on:
  pull_request:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Format Check
        run: npm run format:check

      - name: Type Check
        run: npm run typecheck

Best practice: Run lint/format checks first. They’re fast and catch the obvious stuff.

Test Suite

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# .github/workflows/test.yml
name: Test

on:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install
        run: npm ci

      - name: Test
        run: npm test -- --coverage

      - name: Upload Coverage
        uses: codecov/codecov-action@v4
        with:
          fail_ci_if_error: false  # Don't block on coverage service issues

Best practice: Parallelize tests when possible. Use test splitting for large suites.

1
2
3
4
5
6
7
8
9
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - name: Test
        run: npm test -- --shard=${{ matrix.shard }}/4

Layer 3: Security Scanning

Security checks that don’t slow down development.

Secrets Detection (Pre-Commit)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# .github/workflows/security.yml
name: Security

on:
  pull_request:

jobs:
  secrets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Dependency Scanning

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  dependencies:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Dependency Review
        uses: actions/dependency-review-action@v4
        with:
          fail-on-severity: high
          allow-licenses: MIT, Apache-2.0, BSD-3-Clause

SAST (Static Analysis)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Semgrep
        uses: returntocorp/semgrep-action@v1
        with:
          config: >-
            p/security-audit
            p/secrets

Best practice: Set appropriate severity thresholds. Don’t block on low-severity issues.

Layer 4: AI-Powered Review

Add AI analysis for nuanced feedback.

CodeRabbit Integration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# .coderabbit.yaml
reviews:
  request_changes_workflow: false
  high_level_summary: true

  ignore:
    paths:
      - "**/*.test.*"
      - "**/fixtures/**"
      - "docs/**"

  auto_review:
    enabled: true
    drafts: false

chat:
  auto_reply: true

Custom AI Review

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# .github/workflows/ai-review.yml
name: AI Review

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  ai-review:
    runs-on: ubuntu-latest
    if: github.event.pull_request.draft == false
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get Changed Files
        id: files
        run: |
          echo "files=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | head -20 | tr '\n' ' ')" >> $GITHUB_OUTPUT

      - name: AI Analysis
        if: steps.files.outputs.files != ''
        uses: your-org/ai-review-action@v1
        with:
          files: ${{ steps.files.outputs.files }}
          severity_threshold: medium
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Layer 5: Merge Requirements

Configure what’s required to merge.

Required Checks

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Reasonable requirements for most teams
required_checks:
  - lint           # Fast, always run
  - test           # Essential
  - security       # Non-negotiable
  - build          # Verify it compiles

optional_checks:
  - ai-review      # Helpful but not blocking
  - coverage       # Informational
  - performance    # Run on specific paths only

Conditional Requirements

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# .github/workflows/conditional.yml
name: Conditional Checks

on:
  pull_request:

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      backend: ${{ steps.filter.outputs.backend }}
      frontend: ${{ steps.filter.outputs.frontend }}
      infra: ${{ steps.filter.outputs.infra }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v2
        id: filter
        with:
          filters: |
            backend:
              - 'src/api/**'
              - 'src/services/**'
            frontend:
              - 'src/components/**'
              - 'src/pages/**'
            infra:
              - 'terraform/**'
              - 'k8s/**'

  backend-tests:
    needs: detect-changes
    if: needs.detect-changes.outputs.backend == 'true'
    runs-on: ubuntu-latest
    steps:
      - name: Run backend tests
        run: npm run test:backend

  frontend-tests:
    needs: detect-changes
    if: needs.detect-changes.outputs.frontend == 'true'
    runs-on: ubuntu-latest
    steps:
      - name: Run frontend tests
        run: npm run test:frontend

  infra-validation:
    needs: detect-changes
    if: needs.detect-changes.outputs.infra == 'true'
    runs-on: ubuntu-latest
    steps:
      - name: Validate Terraform
        run: terraform validate

Speed Optimization

Caching

1
2
3
4
5
6
7
8
9
- name: Cache Dependencies
  uses: actions/cache@v4
  with:
    path: |
      ~/.npm
      node_modules
    key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-npm-

Parallel Jobs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
jobs:
  lint:
    runs-on: ubuntu-latest
    # ... lint steps

  test:
    runs-on: ubuntu-latest
    # ... test steps

  security:
    runs-on: ubuntu-latest
    # ... security steps

  # These all run in parallel

Skip Unnecessary Work

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
- name: Check if build needed
  id: check
  run: |
    if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -qE '\.(ts|tsx|js|jsx)$'; then
      echo "needed=true" >> $GITHUB_OUTPUT
    else
      echo "needed=false" >> $GITHUB_OUTPUT
    fi

- name: Build
  if: steps.check.outputs.needed == 'true'
  run: npm run build

Handling Edge Cases

Draft PRs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
on:
  pull_request:
    types: [opened, synchronize, ready_for_review]

jobs:
  full-check:
    if: github.event.pull_request.draft == false
    # ... full checks

  quick-check:
    if: github.event.pull_request.draft == true
    # ... just lint and type check

Emergency Bypasses

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Allow bypass for emergencies (with audit trail)
jobs:
  check:
    if: "!contains(github.event.pull_request.labels.*.name, 'emergency')"
    # ... normal checks

  audit-bypass:
    if: contains(github.event.pull_request.labels.*.name, 'emergency')
    runs-on: ubuntu-latest
    steps:
      - name: Log Bypass
        run: |
          echo "Emergency bypass by ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
          # Send to audit log

Large PRs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
- name: Check PR Size
  uses: actions/github-script@v7
  with:
    script: |
      const { data: files } = await github.rest.pulls.listFiles({
        owner: context.repo.owner,
        repo: context.repo.repo,
        pull_number: context.issue.number
      });

      const additions = files.reduce((sum, f) => sum + f.additions, 0);
      const deletions = files.reduce((sum, f) => sum + f.deletions, 0);

      if (additions + deletions > 1000) {
        await github.rest.issues.createComment({
          owner: context.repo.owner,
          repo: context.repo.repo,
          issue_number: context.issue.number,
          body: '> This PR has over 1000 lines changed. Consider splitting it for easier review.'
        });
      }

Notification Best Practices

Slack Integration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
- name: Notify on Failure
  if: failure()
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "PR Check Failed",
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*<${{ github.event.pull_request.html_url }}|${{ github.event.pull_request.title }}>* failed checks"
            }
          }
        ]
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

PR Comments

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
- name: Post Summary
  uses: actions/github-script@v7
  with:
    script: |
      await github.rest.issues.createComment({
        owner: context.repo.owner,
        repo: context.repo.repo,
        issue_number: context.issue.number,
        body: `## Check Summary
        - Lint: Passed
        - Tests: 142 passed, 0 failed
        - Coverage: 87%
        - Security: No issues found`
      });

FAQ

How long should CI take?

Target under 10 minutes for the full suite, under 5 minutes for basic checks. Anything over 15 minutes will frustrate developers and reduce usage.

Should AI review be a required check?

Usually no. AI review is helpful but can have false positives. Make it informational, not blocking. Let humans decide whether to act on AI suggestions.

How do I handle flaky tests?

Quarantine them. Move flaky tests to a separate job that doesn’t block merges. Fix them as a priority, but don’t let them slow everyone down.

What about monorepos?

Use path-based triggering to only run relevant checks. Implement affected project detection. Consider tools like Nx or Turborepo for intelligent caching.

Conclusion

Key Takeaways

  • Start with branch protection and CODEOWNERS
  • Run fast checks (lint, type) first
  • Parallelize independent jobs
  • Use path-based filtering to skip unnecessary work
  • Cache dependencies aggressively
  • Make security checks non-blocking but visible
  • Add AI review as informational, not required
  • Provide emergency bypass with audit trail
  • Target under 10 minutes for full CI
  • Notify on failures, not every run

AI Coding Security Insights.
Ship Vibe-Coded Apps Safely.

Effortlessly test and evaluate web application security using Vibe Eval agents.