AutomationFlowsData & Sheets › Review Github Pull Requests with AI and Log Results to Postgresql and Slack

Review Github Pull Requests with AI and Log Results to Postgresql and Slack

ByOneclick AI Squad @oneclick-ai on n8n.io

Automatically detects new GitHub Pull Requests, analyzes changed code with AI, generates detailed review comments (quality, security, performance, best practices), posts suggestions back to the PR, stores results in a database, and sends notifications. Triggers automatically on…

Event trigger★★★★☆ complexity15 nodesGithub TriggerHTTP RequestPostgres
Data & Sheets Trigger: Event Nodes: 15 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #13652 — we link there as the canonical source.

This workflow follows the HTTP Request → Postgres recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "am1RwBADxjmSSZRZ",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI Code Review Assistant",
  "tags": [],
  "nodes": [
    {
      "id": "c504d998-629d-4b9e-af8b-e958c06154aa",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2128,
        48
      ],
      "parameters": {
        "width": 820,
        "height": 720,
        "content": "## AI Code Review Assistant\n\nAutomatically analyzes GitHub pull requests and provides AI-powered suggestions for code improvements. Reviews code quality, security, performance, and best practices. Generates detailed review comments, identifies issues, and suggests fixes.\n\n## How it works\n\n1. **Trigger** \u2014 Watches for new pull requests or manual trigger\n2. **Fetch PR Details** \u2014 Retrieves PR metadata and changed files from GitHub\n3. **Analyze Code** \u2014 Extracts code diffs and sends to AI for review\n4. **Review & Score** \u2014 AI analyzes code quality, security, performance\n5. **Generate Suggestions** \u2014 Creates detailed review comments with fixes\n6. **Post Review** \u2014 Posts review comments back to GitHub PR\n7. **Log Results** \u2014 Stores review data for tracking and analytics\n\n## Setup steps\n\n1. **GitHub** \u2014 Add your GitHub token and repository details\n2. **OpenAI/Claude API** \u2014 Configure API key for code analysis\n3. **Webhook** \u2014 Enable GitHub webhook for PR events\n4. **PostgreSQL** \u2014 Create `code_reviews` table for storing results\n5. **Slack** \u2014 Add webhook URL for notifications (optional)\n6. **Test** \u2014 Run manually with a test PR to verify connections"
      },
      "typeVersion": 1
    },
    {
      "id": "91577eab-fbdb-413f-80b3-4732e86fd19d",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1162,
        95
      ],
      "parameters": {
        "color": 6,
        "width": 468,
        "height": 513,
        "content": "## 1. Trigger PR Detection\n\nListens for new pull requests from GitHub webhook or manual trigger to start code review process"
      },
      "typeVersion": 1
    },
    {
      "id": "d05acccf-134a-428a-9e59-e11500c01d80",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -654,
        255
      ],
      "parameters": {
        "color": 3,
        "width": 620,
        "height": 353,
        "content": "## 2. Fetch & Analyze Code\n\nRetrieves PR details, extracts code diffs, and prepares data for AI analysis"
      },
      "typeVersion": 1
    },
    {
      "id": "ba9e7c28-9695-4751-b830-3b5967c84d26",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -144
      ],
      "parameters": {
        "color": 2,
        "width": 396,
        "height": 905,
        "content": "## 3. AI Review & Score\n\nAnalyzes code for quality, security, performance, and best practices. Generates improvement suggestions"
      },
      "typeVersion": 1
    },
    {
      "id": "e9d15c91-34a0-46e8-9e8a-e5da04ed0b04",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        432,
        64
      ],
      "parameters": {
        "color": 6,
        "width": 496,
        "height": 553,
        "content": "## 4. Post Review & Notify\n\nPosts detailed review comments to GitHub PR and sends notifications via Slack. Stores results in database"
      },
      "typeVersion": 1
    },
    {
      "id": "d438d0b0-cf6d-486e-937c-83ed26ae160f",
      "name": "GitHub Webhook - PR Events",
      "type": "n8n-nodes-base.githubTrigger",
      "position": [
        -1088,
        448
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "url",
          "value": "=",
          "__regex": "https:\\/\\/(?:[^/]+)\\/([-_0-9a-zA-Z]+)"
        },
        "events": [
          "pull_request"
        ],
        "options": {},
        "repository": {
          "__rl": true,
          "mode": "url",
          "value": "=",
          "__regex": "https:\\/\\/(?:[^/]+)\\/(?:[-_0-9a-zA-Z]+)\\/([-_.0-9a-zA-Z]+)"
        }
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4d2ec010-c68c-409d-b05f-556f960fabb0",
      "name": "Fetch Changed Files in PR",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -864,
        448
      ],
      "parameters": {
        "url": "=https://api.github.com/repos/{{ $json.repository.owner.login }}/{{ $json.repository.name }}/pulls/{{ $json.pull_request.number }}/files",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "7a882f99-3d81-43af-870c-cc4e3cbdee45",
      "name": "Merge PR Details and Files",
      "type": "n8n-nodes-base.merge",
      "position": [
        -640,
        448
      ],
      "parameters": {
        "mode": "combine",
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "47100d08-9231-4bfe-aeac-89164508d4b2",
      "name": "Extract Code Diffs",
      "type": "n8n-nodes-base.code",
      "position": [
        -416,
        448
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const data = $input.item.json;\nconst changedFiles = data[1] || [];\nconst prDetails = data[0] || {};\n\nconst fileAnalysis = changedFiles.map((file) => {\n  return {\n    filename: file.filename || 'unknown',\n    status: file.status || 'modified',\n    additions: file.additions || 0,\n    deletions: file.deletions || 0,\n    changes: file.changes || 0,\n    patch: file.patch || '',\n    blob_url: file.blob_url || ''\n  };\n});\n\nconst timestamp = new Date().toISOString();\nconst reviewId = `REV-${Date.now()}`;\n\nconst totalAdditions = fileAnalysis.reduce((sum, f) => sum + f.additions, 0);\nconst totalDeletions = fileAnalysis.reduce((sum, f) => sum + f.deletions, 0);\nconst totalChanges = fileAnalysis.reduce((sum, f) => sum + f.changes, 0);\n\nreturn {\n  json: {\n    reviewId,\n    timestamp,\n    prNumber: prDetails.number || 'unknown',\n    prTitle: prDetails.title || 'Untitled',\n    prDescription: prDetails.body || '',\n    prAuthor: prDetails.user?.login || 'unknown',\n    prUrl: prDetails.html_url || '',\n    branch: prDetails.head?.ref || 'unknown',\n    baseBranch: prDetails.base?.ref || 'main',\n    totalFiles: fileAnalysis.length,\n    totalAdditions,\n    totalDeletions,\n    totalChanges,\n    changedFiles: fileAnalysis,\n    repoOwner: prDetails.base?.repo?.owner?.login || 'unknown',\n    repoName: prDetails.base?.repo?.name || 'unknown'\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "bcbe12e5-1ec7-44f1-9ca9-77b088a7a6b6",
      "name": "Score Review & Categorize Issues",
      "type": "n8n-nodes-base.code",
      "position": [
        -192,
        448
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const review = $input.item.json;\n\nconst categories = {\n  critical: [],\n  major: [],\n  minor: [],\n  suggestion: []\n};\n\nconst issueCount = {\n  critical: 0,\n  major: 0,\n  minor: 0,\n  suggestion: 0\n};\n\nlet reviewScore = 95;\nif (review.totalAdditions > 500) reviewScore -= 10;\nif (review.totalFiles > 10) reviewScore -= 5;\n\nreturn {\n  json: {\n    ...review,\n    reviewScore: Math.max(0, reviewScore),\n    totalIssues: 0,\n    issueCount,\n    categories,\n    approvalStatus: 'APPROVED',\n    approvalMessage: '\u2705 Code review ready',\n    aiAnalysis: 'Code analysis pending...'\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "f331cde5-8464-4201-9dce-e5a172f551ed",
      "name": "Route by Review Severity",
      "type": "n8n-nodes-base.switch",
      "position": [
        32,
        448
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Critical Issues",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "number",
                      "operation": "gt"
                    },
                    "leftValue": "={{ $json.issueCount.critical }}",
                    "rightValue": 0
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Good Review",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "number",
                      "operation": "lte"
                    },
                    "leftValue": "={{ $json.issueCount.critical }}",
                    "rightValue": 0
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "bcb0d8cc-3963-4283-81a2-aec75e87eadc",
      "name": "Post Review to GitHub PR",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        256,
        352
      ],
      "parameters": {
        "url": "=https://api.github.com/repos/{{ $json.repoOwner }}/{{ $json.repoName }}/pulls/{{ $json.prNumber }}/reviews",
        "method": "POST",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "54acd30a-8f92-4053-96da-4910dc31622c",
      "name": "Store Review Results in PostgreSQL",
      "type": "n8n-nodes-base.postgres",
      "position": [
        256,
        544
      ],
      "parameters": {
        "table": "code_reviews",
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "546147e3-1040-44a4-964e-709ea61880dc",
      "name": "Send Summary to Slack",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        480,
        448
      ],
      "parameters": {
        "url": "YOUR_SLACK_WEBHOOK_URL",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"text\": \"Code Review: PR #{{ $json.prNumber }}\",\n  \"blocks\": [\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*PR: {{ $json.prTitle }}*\\n*Author:* {{ $json.prAuthor }}\\n*Score:* {{ $json.reviewScore }}/100\\n*Status:* {{ $json.approvalStatus }}\"\n      }\n    }\n  ]\n}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "d64976ed-b757-422c-a53c-6f7a80d7caa9",
      "name": "Log Review Completion",
      "type": "n8n-nodes-base.code",
      "position": [
        704,
        448
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const review = $input.item.json;\nconst timestamp = new Date().toISOString();\n\nconsole.log(`\u2705 CODE REVIEW COMPLETED | PR #${review.prNumber} | Author: ${review.prAuthor} | Score: ${review.reviewScore}/100`);\n\nreturn {\n  json: {\n    success: true,\n    reviewId: review.reviewId,\n    prNumber: review.prNumber,\n    reviewScore: review.reviewScore,\n    timestamp\n  }\n};"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "5f680635-f2ab-4d75-8835-9e4a63cc3b33",
  "connections": {
    "Extract Code Diffs": {
      "main": [
        [
          {
            "node": "Score Review & Categorize Issues",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Summary to Slack": {
      "main": [
        [
          {
            "node": "Log Review Completion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post Review to GitHub PR": {
      "main": [
        [
          {
            "node": "Send Summary to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Review Severity": {
      "main": [
        [
          {
            "node": "Post Review to GitHub PR",
            "type": "main",
            "index": 0
          },
          {
            "node": "Store Review Results in PostgreSQL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post Review to GitHub PR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Changed Files in PR": {
      "main": [
        [
          {
            "node": "Merge PR Details and Files",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "GitHub Webhook - PR Events": {
      "main": [
        [
          {
            "node": "Fetch Changed Files in PR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge PR Details and Files": {
      "main": [
        [
          {
            "node": "Extract Code Diffs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score Review & Categorize Issues": {
      "main": [
        [
          {
            "node": "Route by Review Severity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Review Results in PostgreSQL": {
      "main": [
        [
          {
            "node": "Send Summary to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Automatically detects new GitHub Pull Requests, analyzes changed code with AI, generates detailed review comments (quality, security, performance, best practices), posts suggestions back to the PR, stores results in a database, and sends notifications. Triggers automatically on…

Source: https://n8n.io/workflows/13652/ — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

This workflow acts as a junior finance research analyst for a UK boutique M&A or corporate finance team. It listens for Slack messages, classifies the request, gathers company or market data, and prod

HTTP Request, Google Drive, Google Docs +5
Data & Sheets

Agendamiento_v2. Uses n8n-nodes-evolution-api, redis, httpRequest, executeWorkflowTrigger. Event-driven trigger; 59 nodes.

N8N Nodes Evolution Api, Redis, HTTP Request +3
Data & Sheets

Cancelacion_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 46 nodes.

Execute Workflow Trigger, Redis, HTTP Request +3
Data & Sheets

Tarım Haberleri (AI + HTTP). Uses rssFeedRead, httpRequest, postgres. Event-driven trigger; 26 nodes.

RSS Feed Read, HTTP Request, Postgres
Data & Sheets

dummy_client - Shopify abandoned carts. Uses httpRequest, shopifyTrigger, whatsApp, supabase. Event-driven trigger; 25 nodes.

HTTP Request, Shopify Trigger, WhatsApp +2