AI-Assisted Code Review and PR Analysis

Stack

  • Node.js
  • GitHub
  • Jenkins
  • CodeRabbit
  • GitHub Copilot
  • Docker
  • K3s
  • Ubuntu 22.04 LTS

Table of Contents

  1. Prerequisites and Environment Assumptions
  2. Architecture Overview
  3. System Preparation
  4. Setting Up CodeRabbit for AI PR Reviews
  5. Configuring GitHub Copilot in the Pipeline
  6. Installing and Configuring Jenkins
  7. Integrating AI Review Gates into the Jenkinsfile
  8. Blocking Merges on AI Review Failures
  9. Docker Build and Push Stage
  10. Deploying to K3s
  11. Using the GitHub API to Parse AI Review Signals
  12. Secrets Management
  13. Full Pipeline Execution Walkthrough
  14. Troubleshooting Reference

Prerequisites and Environment Assumptions

What You Need to Know Before Starting

  • Solid Jenkins pipeline (Groovy DSL) experience
  • Familiarity with GitHub Apps, webhooks, and the GitHub REST API
  • Working knowledge of Kubernetes Deployments and rolling update mechanics
  • Understanding of PR-based Git workflows (feature branches, protected branches, required status checks)
  • Basic shell scripting for API response parsing with jq

Infrastructure Assumptions

  • Two Ubuntu 22.04 LTS servers:
    • CI server: Jenkins + Docker, 4 vCPU / 8 GB RAM minimum
    • K3s node: Kubernetes cluster, 2 vCPU / 4 GB RAM minimum
  • GitHub repository with branch protection rules enabled on main
  • Docker Hub account for image registry
  • CodeRabbit account (free tier covers open-source repos; paid tier for private)
  • GitHub Copilot subscription (individual or business)
  • A domain name with DNS control for Ingress/TLS

Required Software Versions

ToolVersion
Ubuntu22.04 LTS
Jenkins2.452.x LTS
Docker Engine26.x
K3sv1.30.x
kubectlmatching K3s minor
jq1.6+
Node.js20.x LTS

Architecture Overview

The core difference from a conventional pipeline is the introduction of AI review gates between code push and deployment. The pipeline does not proceed to build or deploy until AI-generated review signals are resolved.

Developer pushes feature branch to GitHub
         │
         ▼
  Developer opens Pull Request
         │
         ├──► CodeRabbit analyzes PR automatically
         │      - Line-by-line code review
         │      - Security and logic issue flagging
         │      - Summary comment posted to PR
         │
         ├──► GitHub Copilot suggestions surfaced in editor (pre-push)
         │
         ▼
  Jenkins pipeline triggered on PR creation/update
         │
         ├── Stage: Checkout
         ├── Stage: Install & Lint
         ├── Stage: Tests
         ├── Stage: AI Review Gate (poll GitHub API for CodeRabbit verdict)
         │      └── BLOCK if AI flagged critical issues
         ├── Stage: Build Docker Image
         ├── Stage: Push to Registry
         └── Stage: Deploy to K3s (only on merge to main)

AI-assisted DevOps pipeline automation happens asynchronously via GitHub. Jenkins polls the GitHub API to read CodeRabbit’s review state before allowing the pipeline to continue past the gate stage.

System Preparation

Run on both servers unless noted.

Update and Install Base Packages

sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y \
  curl \
  gnupg \
  ca-certificates \
  lsb-release \
  apt-transport-https \
  software-properties-common \
  git \
  jq \
  unzip \
  ufw

Configure UFW

On the CI server:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 8080/tcp
sudo ufw allow 50000/tcp
sudo ufw enable

On the K3s node:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 6443/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 10250/tcp
sudo ufw enable

Install Docker on the CI Server

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin

Setting Up CodeRabbit for AI PR Reviews

CodeRabbit is a GitHub App that performs automatic AI-powered code review on every pull request. It posts inline comments and a summary review, and can be configured to approve or request changes.

Install the CodeRabbit GitHub App

  1. Navigate to coderabbit.ai and sign in with your GitHub account.
  2. Click Add to GitHub and select the repository or organization.
  3. Grant the requested permissions:
    • Read access to code, pull requests, issues
    • Write access to pull request reviews and comments
  4. Complete the installation. CodeRabbit will now automatically trigger on every new PR in the selected repositories.

Configure CodeRabbit via .coderabbit.yaml

CodeRabbit’s behavior is controlled by a config file committed to your repository root. Create .coderabbit.yaml:

# .coderabbit.yaml
version: "2"

reviews:
  # Automatically request changes when critical issues are found
  request_changes_workflow: true

  # Set review profile: chill (lenient) | assertive (strict) | informative (comments only)
  profile: "assertive"

  # Review all changed files, not just the diff
  review_status: true

  # Post a high-level walkthrough comment on every PR
  high_level_summary: true

  # Enable path-specific review instructions
  path_instructions:
    - path: "src/**/*.js"
      instructions: |
        Focus on: security vulnerabilities, async/await error handling,
        input validation, and N+1 query patterns.
    - path: "k8s/**/*.yaml"
      instructions: |
        Check for: missing resource limits, missing readiness probes,
        use of latest image tags, and overly permissive RBAC.
    - path: "Dockerfile"
      instructions: |
        Check for: non-root user enforcement, pinned base image tags,
        secrets in ENV instructions, and unnecessary packages.

  # Files to exclude from AI review
  path_filters:
    - "!package-lock.json"
    - "!**/*.test.js"
    - "!coverage/**"

chat:
  # Allow team members to ask CodeRabbit follow-up questions in PR comments
  auto_reply: true

Commit and push this file:

git add .coderabbit.yaml
git commit -m "chore: add CodeRabbit configuration"
git push origin main

Verify CodeRabbit Is Working

Open a test pull request with a deliberate issue (e.g., an unhandled promise rejection or a hardcoded credential string). Within 1-3 minutes, CodeRabbit should post:

  • An inline comment on the offending line
  • A PR-level summary comment
  • A review with Changes Requested status if the assertive profile detected issues

Check the PR’s Checks tab to confirm the CodeRabbit review status is being reported as a GitHub check.

Configuring GitHub Copilot in the Pipeline

GitHub Copilot operates primarily in the developer’s editor (VS Code, JetBrains, Neovim). Its role in the pipeline is enforced through pre-push hooks and team workflow standards rather than direct CI integration. Here is how to operationalize it.

Enable Copilot for Your Organization

Go to your GitHub organization: Settings > Copilot > Access. Enable Copilot for all members or specific teams. Seat assignment takes effect immediately.

Install Copilot CLI on the CI Server (Optional Linting Aid)

GitHub Copilot CLI provides AI-powered command suggestions. While primarily a developer tool, it can be used in CI to explain failing test output or suggest fixes in automated comments.

# Install GitHub CLI first
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
  | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] \
  https://cli.github.com/packages stable main" \
  | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null

sudo apt-get update
sudo apt-get install -y gh

# Authenticate (use a service account token for CI)
echo "<GITHUB_TOKEN>" | gh auth login --with-token

# Install Copilot CLI extension
gh extension install github/gh-copilot
gh copilot --version

Enforce Copilot Usage via Pre-Push Hook (Developer Workstations)

Distribute this hook via your repo’s scripts/ directory and document it in the README as a required setup step.

Create scripts/install-hooks.sh:

#!/bin/bash
# Run once after cloning: bash scripts/install-hooks.sh

HOOK_DIR="$(git rev-parse --git-dir)/hooks"

cat > "$HOOK_DIR/pre-push" << 'EOF'
#!/bin/bash
# Remind developers to address Copilot suggestions before pushing
echo ""
echo "Pre-push checklist:"
echo "  [ ] Copilot suggestions reviewed and addressed"
echo "  [ ] No hardcoded secrets or credentials"
echo "  [ ] Unit tests updated for changed functions"
echo ""
read -p "Continue push? (y/n): " confirm
[ "$confirm" = "y" ] || exit 1
EOF

chmod +x "$HOOK_DIR/pre-push"
echo "Git hooks installed."
chmod +x scripts/install-hooks.sh
git add scripts/install-hooks.sh
git commit -m "chore: add git hooks installer"

Installing and Configuring Jenkins

Install Java

sudo apt-get install -y openjdk-21-jdk
java -version
echo 'export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64' | sudo tee -a /etc/environment
source /etc/environment

Install Jenkins

curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key \
  | sudo tee /usr/share/keyrings/jenkins-keyring.asc > /dev/null

echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
  https://pkg.jenkins.io/debian-stable binary/" \
  | sudo tee /etc/apt/sources.list.d/jenkins.list > /dev/null

sudo apt-get update
sudo apt-get install -y jenkins

sudo systemctl enable jenkins
sudo systemctl start jenkins
sudo systemctl status jenkins

Retrieve Initial Admin Password

sudo cat /var/lib/jenkins/secrets/initialAdminPassword

Navigate to http://<ci-server-ip>:8080, complete the setup wizard, and install suggested plugins.

Install Additional Plugins

curl -O http://localhost:8080/jnlpJars/jenkins-cli.jar

java -jar jenkins-cli.jar \
  -s http://localhost:8080 \
  -auth admin:<admin-password> \
  install-plugin \
    docker-workflow \
    github \
    credentials-binding \
    http_request \
    --restart

The http_request plugin is used to call the GitHub API from within the Jenkinsfile to read CodeRabbit review state.

Allow Jenkins to Run Docker

sudo usermod -aG docker jenkins
sudo systemctl restart jenkins
sudo -u jenkins docker ps

Store Credentials in Jenkins

Navigate to Manage Jenkins > Credentials > System > Global credentials and add:

IDKindValue
dockerhub-credentialsUsername with passwordDocker Hub username + access token
github-tokenSecret textGitHub personal access token with repo and pull_requests scopes
kubeconfig-k3sSecret fileContents of /etc/rancher/k3s/k3s.yaml (with server IP corrected)

Integrating AI Review Gates into the Jenkinsfile

The AI review gate works as follows:

  1. Jenkins detects a PR event via GitHub webhook.
  2. After tests pass, Jenkins calls the GitHub Reviews API to check whether CodeRabbit has posted a review with CHANGES_REQUESTED state.
  3. If CodeRabbit has requested changes, the pipeline fails the gate stage and posts a comment back to the PR explaining the block.
  4. If CodeRabbit approved or only left comments (no blocking review), the pipeline continues.

Create Jenkinsfile at the repository root:

pipeline {
  agent any

  environment {
    DOCKER_REGISTRY    = 'docker.io'
    DOCKER_IMAGE_NAME  = 'yourdockerhubuser/myapp'
    DOCKER_CREDENTIALS = 'dockerhub-credentials'
    GITHUB_TOKEN_ID    = 'github-token'
    GITHUB_REPO        = 'your-org/your-repo'
    K8S_NAMESPACE      = 'production'
    K8S_DEPLOYMENT     = 'myapp'
    CODERABBIT_BOT     = 'coderabbitai[bot]'
  }

  options {
    disableConcurrentBuilds()
    timeout(time: 30, unit: 'MINUTES')
    buildDiscarder(logRotator(numToKeepStr: '10'))
  }

  stages {

    stage('Checkout') {
      steps {
        checkout scm
        script {
          env.GIT_SHORT_SHA = sh(
            script: 'git rev-parse --short HEAD',
            returnStdout: true
          ).trim()
          env.IMAGE_TAG = "${env.DOCKER_IMAGE_NAME}:${env.GIT_SHORT_SHA}"
          env.IMAGE_TAG_LATEST = "${env.DOCKER_IMAGE_NAME}:latest"

          // Capture PR number from the GitHub webhook environment variable
          // Jenkins GitHub plugin sets CHANGE_ID for PR builds
          env.PR_NUMBER = env.CHANGE_ID ?: ''
          echo "PR number: ${env.PR_NUMBER}"
          echo "Building image: ${env.IMAGE_TAG}"
        }
      }
    }

    stage('Install Dependencies') {
      steps {
        sh '''
          node --version
          npm --version
          npm ci
        '''
      }
    }

    stage('Lint') {
      steps {
        sh 'npm run lint'
      }
    }

    stage('Test') {
      steps {
        sh 'npm test -- --ci --coverage'
      }
      post {
        always {
          junit allowEmptyResults: true, testResults: 'test-results/*.xml'
        }
      }
    }

    stage('AI Review Gate') {
      // Only enforce the gate on pull request builds, not direct pushes to main
      when {
        expression { return env.PR_NUMBER != '' }
      }
      steps {
        script {
          withCredentials([string(credentialsId: env.GITHUB_TOKEN_ID, variable: 'GH_TOKEN')]) {
            // Poll CodeRabbit review state with retries
            // CodeRabbit may take 1-3 minutes to post its review after the PR is opened
            def maxAttempts = 12
            def attempt = 0
            def reviewState = 'PENDING'

            while (attempt < maxAttempts && reviewState == 'PENDING') {
              attempt++
              echo "Polling GitHub Reviews API - attempt ${attempt}/${maxAttempts}"

              def apiResponse = sh(
                script: """
                  curl -s -H "Authorization: token ${GH_TOKEN}" \
                    -H "Accept: application/vnd.github+json" \
                    "https://api.github.com/repos/${env.GITHUB_REPO}/pulls/${env.PR_NUMBER}/reviews"
                """,
                returnStdout: true
              ).trim()

              // Extract the most recent review state from CodeRabbit bot
              def codeRabbitState = sh(
                script: """
                  echo '${apiResponse}' | jq -r '
                    [ .[] | select(.user.login == "${env.CODERABBIT_BOT}") ] |
                    last |
                    .state // "PENDING"
                  '
                """,
                returnStdout: true
              ).trim()

              echo "CodeRabbit review state: ${codeRabbitState}"

              if (codeRabbitState == 'CHANGES_REQUESTED') {
                reviewState = 'BLOCKED'
              } else if (codeRabbitState == 'APPROVED' || codeRabbitState == 'COMMENTED') {
                reviewState = 'PASSED'
              } else {
                // PENDING or no review yet - wait and retry
                if (attempt < maxAttempts) {
                  echo "No CodeRabbit review yet, waiting 30 seconds..."
                  sleep(30)
                }
              }
            }

            if (reviewState == 'BLOCKED') {
              // Post a comment to the PR explaining the block
              sh """
                curl -s -X POST \
                  -H "Authorization: token ${GH_TOKEN}" \
                  -H "Accept: application/vnd.github+json" \
                  "https://api.github.com/repos/${env.GITHUB_REPO}/issues/${env.PR_NUMBER}/comments" \
                  -d '{"body": ":robot: **Jenkins Pipeline Blocked**\\n\\nCodeRabbit has requested changes on this PR. The deployment pipeline will not proceed until all CodeRabbit issues are resolved and the review status changes to Approved or Commented.\\n\\nBuild: ${env.BUILD_URL}"}'
              """
              error("AI Review Gate FAILED: CodeRabbit has requested changes on PR #${env.PR_NUMBER}. Resolve all flagged issues before this pipeline can proceed.")
            } else if (reviewState == 'PENDING') {
              // Timed out waiting for CodeRabbit - treat as non-blocking warning, not a hard fail
              // Change to error() if you want to enforce a hard block on timeout
              echo "WARNING: CodeRabbit review not received within timeout. Proceeding with caution."
            } else {
              echo "AI Review Gate PASSED: CodeRabbit review state is ${reviewState}"
            }
          }
        }
      }
    }

    stage('Build Docker Image') {
      // Only build after the AI gate passes
      steps {
        sh """
          docker build \
            --label git-commit=${env.GIT_SHORT_SHA} \
            --label build-date=\$(date -u +%Y-%m-%dT%H:%M:%SZ) \
            -t ${env.IMAGE_TAG} \
            -t ${env.IMAGE_TAG_LATEST} \
            .
        """
      }
    }

    stage('Push to Registry') {
      steps {
        withCredentials([usernamePassword(
          credentialsId: env.DOCKER_CREDENTIALS,
          usernameVariable: 'DOCKER_USER',
          passwordVariable: 'DOCKER_PASS'
        )]) {
          sh """
            echo "\$DOCKER_PASS" | docker login -u "\$DOCKER_USER" --password-stdin ${env.DOCKER_REGISTRY}
            docker push ${env.IMAGE_TAG}
            docker push ${env.IMAGE_TAG_LATEST}
          """
        }
      }
      post {
        always {
          sh 'docker logout'
        }
      }
    }

    stage('Deploy to Kubernetes') {
      // Only deploy on merges to main, not on PR builds
      when {
        branch 'main'
      }
      steps {
        withCredentials([file(credentialsId: 'kubeconfig-k3s', variable: 'KUBECONFIG')]) {
          sh """
            kubectl set image deployment/${env.K8S_DEPLOYMENT} \
              ${env.K8S_DEPLOYMENT}=${env.IMAGE_TAG} \
              -n ${env.K8S_NAMESPACE}

            kubectl rollout status deployment/${env.K8S_DEPLOYMENT} \
              -n ${env.K8S_NAMESPACE} \
              --timeout=300s
          """
        }
      }
    }

  }

  post {
    success {
      echo "Pipeline completed. Image: ${env.IMAGE_TAG}"
    }
    failure {
      echo "Pipeline failed. Check stage logs above."
    }
    cleanup {
      sh """
        docker rmi ${env.IMAGE_TAG} || true
        docker rmi ${env.IMAGE_TAG_LATEST} || true
        docker image prune -f
      """
    }
  }
}

Blocking Merges on AI Review Failures

The Jenkinsfile gate above fails the pipeline build, but you also need to prevent the PR from being merged on GitHub regardless of whether someone bypasses Jenkins. This is enforced at the GitHub branch protection level.

Configure Branch Protection on main

Go to your repository: Settings > Branches > Add rule

  • Branch name pattern: main
  • Enable: Require a pull request before merging
  • Enable: Require approvals: 1 minimum
  • Enable: Require status checks to pass before merging
    • Search for and add: continuous-integration/jenkins/pr-merge (Jenkins reports this status)
    • Search for and add: CodeRabbit (CodeRabbit reports a check status)
  • Enable: Require branches to be up to date before merging
  • Enable: Do not allow bypassing the above settings (applies to admins too)

Click Save changes.

Now both Jenkins and CodeRabbit must report green before the Merge button is enabled on any PR.

8.2 Configure CodeRabbit to Report a Blocking Check

In .coderabbit.yaml, ensure the review mode is set to post a formal review (not just comments), which triggers a GitHub check status:

reviews:
  request_changes_workflow: true
  # This setting causes CodeRabbit to post a formal "Request Changes" review
  # which GitHub reads as a blocking check when branch protection is enabled

With request_changes_workflow: true and branch protection requiring CodeRabbit’s check, a PR with unresolved AI issues literally cannot be merged (the button is grayed out).

9. Docker Build and Push Stage

The Dockerfile for a Node.js application:

FROM node:20-alpine AS builder

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY src/ ./src/

FROM node:20-alpine AS production

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/src ./src
COPY --chown=appuser:appgroup package.json ./

USER appuser
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "src/index.js"]

Build locally to verify before committing:

docker build -t myapp:local .
docker run --rm -p 3000:3000 myapp:local
curl http://localhost:3000/health

Deploying to K3s

Install K3s

On the K3s node:

curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \
  --disable traefik \
  --tls-san <k3s-node-public-ip>" sh -

sudo systemctl status k3s
sudo kubectl get nodes

Distribute kubeconfig to CI Server

On the K3s node:

sudo cat /etc/rancher/k3s/k3s.yaml

On the CI server:

sudo mkdir -p /var/lib/jenkins/.kube
# Paste the k3s.yaml content, then fix the server address:
sudo sed -i 's/127.0.0.1/<k3s-node-ip>/g' /var/lib/jenkins/.kube/config
sudo chown -R jenkins:jenkins /var/lib/jenkins/.kube
sudo chmod 600 /var/lib/jenkins/.kube/config

# Verify
sudo -u jenkins kubectl get nodes

Kubernetes Manifests

k8s/namespace.yaml:

apiVersion: v1
kind: Namespace
metadata:
  name: production

k8s/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: myapp
    spec:
      terminationGracePeriodSeconds: 30
      containers:
      - name: myapp
        image: yourdockerhubuser/myapp:latest
        ports:
        - containerPort: 3000
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "256Mi"
        readinessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 15
          periodSeconds: 20

k8s/service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: production
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 3000
  type: ClusterIP

Apply the initial manifests:

kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml

Using the GitHub API to Parse AI Review Signals

This section details the GitHub API calls used in the pipeline gate stage so you can extend or debug them.

List Reviews on a PR

export GH_TOKEN="your-github-token"
export REPO="your-org/your-repo"
export PR_NUMBER="42"

curl -s \
  -H "Authorization: token $GH_TOKEN" \
  -H "Accept: application/vnd.github+json" \
  "https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}/reviews" \
  | jq '.'

A review object looks like this:

{
  "id": 12345678,
  "user": {
    "login": "coderabbitai[bot]"
  },
  "body": "**Actionable comments posted: 2**...",
  "state": "CHANGES_REQUESTED",
  "submitted_at": "2024-11-01T10:23:45Z"
}

Possible state values: APPROVED, CHANGES_REQUESTED, COMMENTED, DISMISSED, PENDING.

Extract Only the Latest CodeRabbit Review State

curl -s \
  -H "Authorization: token $GH_TOKEN" \
  -H "Accept: application/vnd.github+json" \
  "https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}/reviews" \
  | jq -r '
      [ .[] | select(.user.login == "coderabbitai[bot]") ] |
      last |
      .state // "PENDING"
    '

This filters to only CodeRabbit’s reviews, takes the most recent one, and returns its state. If no review has been posted yet, it returns PENDING.

Post a Comment Back to the PR

Used in the pipeline to explain a gate failure to the developer:

curl -s -X POST \
  -H "Authorization: token $GH_TOKEN" \
  -H "Accept: application/vnd.github+json" \
  "https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments" \
  -d '{
    "body": ":robot: Pipeline blocked: CodeRabbit has requested changes. Resolve all flagged issues before retrying."
  }'

Check PR Review Comments Count (Optional Metric)

If you want to surface in build output how many issues CodeRabbit found:

curl -s \
  -H "Authorization: token $GH_TOKEN" \
  -H "Accept: application/vnd.github+json" \
  "https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}/comments" \
  | jq '[.[] | select(.user.login == "coderabbitai[bot]")] | length'

This lets you log or threshold on comment count: e.g., fail the gate if more than 5 inline issues were flagged even if the review state is COMMENTED rather than CHANGES_REQUESTED.

Secrets Management

GitHub Token Scopes

The GitHub personal access token (or GitHub App installation token) used by Jenkins for API calls needs these scopes:

  • repo (full repository access)
  • pull_requests (read and write)

Create it at GitHub > Settings > Developer settings > Personal access tokens > Tokens (classic). Store it in Jenkins as a Secret text credential with ID github-token.

Kubernetes Secrets for the Application

kubectl create secret generic myapp-secrets \
  --namespace production \
  --from-literal=db-password='your-db-password-here'

kubectl get secret myapp-secrets -n production

Reference in the Deployment spec:

env:
- name: DB_PASSWORD
  valueFrom:
    secretKeyRef:
      name: myapp-secrets
      key: db-password

12.3 Never Log Tokens in Jenkins

The withCredentials block in the Jenkinsfile masks credential values in console output. Always wrap any shell command that uses a token inside withCredentials. Never use echo to print a token value, even for debugging. Jenkins masks it in output but it still constitutes a bad practice and may surface in third-party log shippers.

Full Pipeline Execution Walkthrough

Triggering the Pipeline on a PR

git checkout -b feature/new-endpoint
# Make changes
git add .
git commit -m "feat: add /users endpoint"
git push origin feature/new-endpoint

Open a pull request on GitHub. This triggers:

  1. CodeRabbit starts analyzing the PR (asynchronously, 1-3 minutes).
  2. Jenkins pipeline starts via webhook (immediately).
  3. Jenkins runs Checkout, Install, Lint, Test stages.
  4. Jenkins enters the AI Review Gate stage and begins polling for CodeRabbit’s verdict.
  5. If CodeRabbit finds issues, the pipeline fails at the gate and posts a comment.
  6. Developer resolves the issues, pushes more commits to the branch.
  7. New push re-triggers the pipeline. CodeRabbit re-reviews the updated diff.
  8. Once CodeRabbit approves (or posts only comments), the gate passes.
  9. Docker image is built and pushed with the git SHA tag.
  10. On merge to main, the Deploy stage fires and updates the Kubernetes Deployment.

Monitoring the Rollout

# Watch pods update in real time
kubectl rollout status deployment/myapp -n production -w

# Confirm the new image SHA is running
kubectl get pods -n production \
  -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].image}{"\n"}{end}'

Rolling Back

kubectl rollout undo deployment/myapp -n production
kubectl rollout status deployment/myapp -n production

Troubleshooting Reference

CodeRabbit not posting a review

# Verify the GitHub App is installed on the repository
# Go to: github.com/settings/installations > coderabbitai

# Check if .coderabbit.yaml has YAML syntax errors
cat .coderabbit.yaml | python3 -c "import sys, yaml; yaml.safe_load(sys.stdin)" && echo "YAML valid"

# Confirm the PR is not a draft (CodeRabbit skips draft PRs by default)
# Convert draft to Ready for Review on GitHub

Jenkins AI Review Gate times out waiting for CodeRabbit

The gate polls for up to 6 minutes (12 attempts x 30 seconds). If CodeRabbit consistently takes longer, increase maxAttempts or sleep() in the gate stage. If CodeRabbit is down or rate-limited, the gate currently falls through with a warning. Change this to error() if you want a hard fail on timeout:

} else if (reviewState == 'PENDING') {
  error("AI Review Gate TIMED OUT: No CodeRabbit review received within 6 minutes.")
}

GitHub API returning 401

# Test the token manually
curl -s -H "Authorization: token <your-token>" https://api.github.com/user | jq '.login'
# Should return your GitHub username

# Verify the token is stored correctly in Jenkins
# Manage Jenkins > Credentials > verify ID matches GITHUB_TOKEN_ID in Jenkinsfile

GitHub API returning 403 on PR comments

The token lacks pull_requests write scope. Regenerate the token with the correct scopes and update the Jenkins credential.

jq parse error in gate stage

The API response may contain characters that break shell quoting when interpolated into the jq command. Write the response to a temp file instead:

sh """
  curl -s -H "Authorization: token ${GH_TOKEN}" \
    "https://api.github.com/repos/${env.GITHUB_REPO}/pulls/${env.PR_NUMBER}/reviews" \
    > /tmp/reviews.json

  jq -r '
    [ .[] | select(.user.login == "${env.CODERABBIT_BOT}") ] |
    last | .state // "PENDING"
  ' /tmp/reviews.json
"""

Pipeline deploys on PR builds instead of only on main

The when { branch 'main' } condition in the Deploy stage relies on the Jenkins GitHub plugin correctly setting the BRANCH_NAME environment variable. For PR builds, BRANCH_NAME is set to PR-<number>. Verify:

stage('Debug Branch') {
  steps {
    echo "BRANCH_NAME: ${env.BRANCH_NAME}"
    echo "CHANGE_ID: ${env.CHANGE_ID}"
  }
}

If BRANCH_NAME is not being set, ensure the pipeline job type is Multibranch Pipeline, not a standard Pipeline job. Multibranch Pipeline is required for the Jenkins GitHub plugin to correctly populate PR-specific environment variables.


This pipeline enforces a concrete AI quality gate that cannot be bypassed: CodeRabbit must sign off on every pull request before code reaches a Docker image or a production cluster. The combination of GitHub branch protection rules and the Jenkins gate stage creates two independent enforcement points, meaning a developer cannot merge by bypassing Jenkins alone.

Ivan Dabić

A man with a beard and glasses, wearing an orange hoodie and a black cap with a Hard Rock Cafe logo, stands with his arms crossed against a plain white background.

Ivan Dabić

Co-founder and CEO of BlueGrid.io, with a background in cloud infrastructure, distributed systems, monitoring, and security operations. He works closely with engineering teams to build and operate reliable systems while documenting both technical and organizational aspects of modern engineering work.

Ivan is a metalhead, and big fan of cyberpunk move genre. If you are his secret Santa go with Star Wars Lego box!

Share this post

Share this link via

Or copy link