{
  "id": "AlHVikTKJ41ARefk7-ZTm",
  "name": "Automatically review pull requests with AI and post feedback as GitHub comments",
  "tags": [
    {
      "id": "0db9feb574da46e5",
      "name": "ai",
      "createdAt": "2026-04-30T18:38:11.353194Z",
      "updatedAt": "2026-04-30T18:38:11.353194Z"
    },
    {
      "id": "488c1574bb33409a",
      "name": "gpt-4",
      "createdAt": "2026-04-30T18:38:11.353194Z",
      "updatedAt": "2026-04-30T18:38:11.353194Z"
    },
    {
      "id": "62d32ecd261f4fce",
      "name": "openai",
      "createdAt": "2026-04-30T18:38:11.353194Z",
      "updatedAt": "2026-04-30T18:38:11.353194Z"
    },
    {
      "id": "tanWNWkQaVKBvG4h",
      "name": "github",
      "createdAt": "2026-04-22T22:16:32.332Z",
      "updatedAt": "2026-04-22T22:16:32.332Z"
    },
    {
      "id": "929db4748f034b67",
      "name": "slack",
      "createdAt": "2026-04-30T18:38:11.353194Z",
      "updatedAt": "2026-04-30T18:38:11.353194Z"
    },
    {
      "id": "a0291087c4a844c1",
      "name": "code-review",
      "createdAt": "2026-04-30T18:38:11.353194Z",
      "updatedAt": "2026-04-30T18:38:11.353194Z"
    },
    {
      "id": "de8ac33010a74fe5",
      "name": "devops",
      "createdAt": "2026-04-30T18:38:11.353194Z",
      "updatedAt": "2026-04-30T18:38:11.353194Z"
    },
    {
      "id": "xkhvijdyzZQ1znPF",
      "name": "automation",
      "createdAt": "2026-04-22T22:16:32.341Z",
      "updatedAt": "2026-04-22T22:16:32.341Z"
    }
  ],
  "nodes": [
    {
      "id": "2ad2f601-2b50-4ef6-87c2-2cbabc173ed6",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1872,
        -64
      ],
      "parameters": {
        "width": 480,
        "height": 688,
        "content": "## Automatically review pull requests with AI and post feedback as GitHub comments\n\n## How it works\n\n1. Initiates on a GitHub pull request event trigger.\n2. Filters pull request events to continue with only 'opened' actions.\n3. Retrieves the pull request diff via HTTP request.\n4. Formats the diff, then uses GPT-4o to automatically review the code.\n5. Generates and posts a comment with the review results on GitHub and sends alerts on Slack based on the review severity.\n\n## Setup steps\n\n- [ ] Configure GitHub credentials for the trigger and comment posting\n- [ ] Set up OpenAI API key for GPT-4o code review\n- [ ] Configure Slack credentials for sending alerts\n\n## Customization\n\nAdjust the sensitivity of severity checks within the conditional branch to customize alert levels."
      },
      "typeVersion": 1
    },
    {
      "id": "88edd9fe-e658-4879-9a5a-b438c3388964",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1312,
        64
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Trigger and filter PR\n\nStarts the workflow on pull request events and filters for 'opened' status."
      },
      "typeVersion": 1
    },
    {
      "id": "4c23501c-512b-4155-a280-515efaf148cb",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -864,
        64
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 304,
        "content": "## Fetch and format diff\n\nRetrieves the diff of the pull request and formats it for AI analysis."
      },
      "typeVersion": 1
    },
    {
      "id": "bb95fe03-4409-4e3a-9d12-65136351fcd5",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -432,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 272,
        "content": "## AI review and comment\n\nUses AI to review the code and formulates a GitHub comment."
      },
      "typeVersion": 1
    },
    {
      "id": "e976ecd2-a43e-45cc-9f08-0312076c835e",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 544,
        "content": "## Severity check and alerts\n\nChecks the review severity and sends alerts to Slack."
      },
      "typeVersion": 1
    },
    {
      "id": "6dac4b91-a5bd-44df-8342-b2eefd358678",
      "name": "When PR Opened",
      "type": "n8n-nodes-base.githubTrigger",
      "position": [
        -1264,
        192
      ],
      "parameters": {
        "owner": "={{ $vars.GITHUB_OWNER }}",
        "events": [
          "pull_request"
        ],
        "options": {},
        "repository": "={{ $vars.GITHUB_REPO }}",
        "authentication": "oAuth2"
      },
      "typeVersion": 1
    },
    {
      "id": "5946aa5f-9949-42ca-adb7-e895e7a740bd",
      "name": "Check PR Open",
      "type": "n8n-nodes-base.if",
      "position": [
        -1040,
        192
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.action }}",
              "value2": "opened"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "24978946-9b3f-4b2f-91f8-2212b6000eb9",
      "name": "Fetch PR Diff",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -816,
        192
      ],
      "parameters": {
        "url": "={{ $json.pull_request.diff_url }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github.v3.diff"
            },
            {
              "name": "Authorization",
              "value": "=Bearer {{ $vars.GITHUB_TOKEN }}"
            }
          ]
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "e38e529b-bf33-4c31-a36b-cc0346934cbc",
      "name": "Format PR Diff",
      "type": "n8n-nodes-base.code",
      "position": [
        -608,
        192
      ],
      "parameters": {
        "jsCode": "const triggerData = $('When PR Opened').first().json;\nconst diffRaw = $input.first().json.data;\n\nconst MAX_DIFF_CHARS = 12000;\nconst diffStr = typeof diffRaw === 'string' ? diffRaw : JSON.stringify(diffRaw);\nconst diff = diffStr.slice(0, MAX_DIFF_CHARS);\nconst truncated = diffStr.length > MAX_DIFF_CHARS;\n\nreturn [{\n  json: {\n    diff,\n    truncated,\n    pr_number: triggerData.number,\n    pr_title: triggerData.pull_request.title,\n    pr_body: triggerData.pull_request.body || '',\n    pr_url: triggerData.pull_request.html_url,\n    repo_full_name: triggerData.repository.full_name,\n    owner: triggerData.repository.owner.login,\n    repo: triggerData.repository.name,\n    author: triggerData.pull_request.user.login,\n    base_branch: triggerData.pull_request.base.ref,\n    head_branch: triggerData.pull_request.head.ref\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "6f11c0ff-49a8-4323-807d-c6a91a47414a",
      "name": "AI Code Review with GPT-4",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -384,
        192
      ],
      "parameters": {
        "resource": "chat"
      },
      "typeVersion": 1.4
    },
    {
      "id": "129f584c-ab65-4bf5-8622-5a57d35bdef5",
      "name": "Build Comment for GitHub",
      "type": "n8n-nodes-base.code",
      "position": [
        -160,
        192
      ],
      "parameters": {
        "jsCode": "const rawContent = $input.first().json.message?.content\n  || $input.first().json.choices?.[0]?.message?.content\n  || '';\nconst prevData = $('Format PR Diff').first().json;\n\nlet review;\ntry {\n  const cleaned = rawContent.replace(/```json|```/g, '').trim();\n  review = JSON.parse(cleaned);\n} catch (e) {\n  review = {\n    severity: 'MEDIUM',\n    summary: 'Review parsing failed. Please inspect the raw AI output.',\n    issues: [],\n    positives: [],\n    overall_score: 5\n  };\n}\n\nconst severityEmoji = { CRITICAL: '\ud83d\udd34', HIGH: '\ud83d\udfe0', MEDIUM: '\ud83d\udfe1', LOW: '\ud83d\udfe2' };\nconst emoji = severityEmoji[review.severity] || '\u26aa';\n\nlet comment = `## ${emoji} AI Code Review \u2014 Severity: **${review.severity}**\\n\\n`;\ncomment += `**Overall Score:** ${review.overall_score}/10\\n\\n`;\ncomment += `### \ud83d\udccb Summary\\n${review.summary}\\n\\n`;\n\nif (review.issues && review.issues.length > 0) {\n  comment += `### \ud83d\udea8 Issues (${review.issues.length})\\n\\n`;\n  for (const issue of review.issues) {\n    const ie = severityEmoji[issue.severity] || '\u26aa';\n    comment += `<details>\\n<summary>${ie} [${issue.severity}] ${issue.category?.toUpperCase()} \u2014 ${issue.file}${issue.line ? ` (line ${issue.line})` : ''}</summary>\\n\\n`;\n    comment += `**Issue:** ${issue.description}\\n\\n**Suggestion:** ${issue.suggestion}\\n\\n</details>\\n\\n`;\n  }\n}\n\nif (review.positives && review.positives.length > 0) {\n  comment += `### \u2705 Positives\\n`;\n  review.positives.forEach(p => { comment += `- ${p}\\n`; });\n  comment += '\\n';\n}\n\ncomment += `---\\n*Reviewed by GPT-4o via n8n \u00b7 [View PR](${prevData.pr_url})*`;\n\nreturn [{\n  json: {\n    ...prevData,\n    review,\n    comment_body: comment,\n    is_critical: review.severity === 'CRITICAL',\n    severity: review.severity,\n    score: review.overall_score\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "adebde26-84a2-4675-b007-022f8824722d",
      "name": "Post Comment to GitHub",
      "type": "n8n-nodes-base.github",
      "position": [
        64,
        192
      ],
      "parameters": {
        "body": "={{ $json.comment_body }}",
        "owner": "={{ $json.owner }}",
        "operation": "createComment",
        "repository": "={{ $json.repo }}",
        "issueNumber": "={{ $json.pr_number }}",
        "authentication": "oAuth2"
      },
      "typeVersion": 1
    },
    {
      "id": "9eca81e5-4a04-4268-86be-c2d45ee95734",
      "name": "Check Critical Severity",
      "type": "n8n-nodes-base.if",
      "position": [
        288,
        192
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $('Build Comment for GitHub').first().json.is_critical }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2adfd0d1-0e9b-4e21-81ba-80a5e11230a7",
      "name": "Alert Critical Issues to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        512,
        64
      ],
      "parameters": {
        "text": "=\ud83d\udea8 *CRITICAL code review alert*\n\n*PR:* <{{ $('Build Comment for GitHub').first().json.pr_url }}|#{{ $('Build Comment for GitHub').first().json.pr_number }} \u2014 {{ $('Build Comment for GitHub').first().json.pr_title }}>\n*Author:* `{{ $('Build Comment for GitHub').first().json.author }}`\n*Repo:* `{{ $('Build Comment for GitHub').first().json.repo_full_name }}`\n*Score:* {{ $('Build Comment for GitHub').first().json.score }}/10\n\n*Summary:*\n{{ $('Build Comment for GitHub').first().json.review.summary }}\n\n\u26a0\ufe0f Immediate review required before merge.",
        "otherOptions": {
          "unfurl_links": false
        },
        "authentication": "oAuth2"
      },
      "typeVersion": 2.1
    },
    {
      "id": "316f8c82-6e75-480b-a867-93782a25fe4f",
      "name": "Share Review Summary on Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        512,
        304
      ],
      "parameters": {
        "text": "=\u2705 *PR review completed*\n\n*PR:* <{{ $('Build Comment for GitHub').first().json.pr_url }}|#{{ $('Build Comment for GitHub').first().json.pr_number }} \u2014 {{ $('Build Comment for GitHub').first().json.pr_title }}>\n*Author:* `{{ $('Build Comment for GitHub').first().json.author }}`\n*Severity:* {{ $('Build Comment for GitHub').first().json.severity }}\n*Score:* {{ $('Build Comment for GitHub').first().json.score }}/10\n\nAI review comment has been posted to the PR.",
        "otherOptions": {
          "unfurl_links": false
        },
        "authentication": "oAuth2"
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "51911461-f6f6-46ca-ad91-d10d36d33f9b",
  "connections": {
    "Check PR Open": {
      "main": [
        [
          {
            "node": "Fetch PR Diff",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch PR Diff": {
      "main": [
        [
          {
            "node": "Format PR Diff",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format PR Diff": {
      "main": [
        [
          {
            "node": "AI Code Review with GPT-4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When PR Opened": {
      "main": [
        [
          {
            "node": "Check PR Open",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post Comment to GitHub": {
      "main": [
        [
          {
            "node": "Check Critical Severity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Critical Severity": {
      "main": [
        [
          {
            "node": "Alert Critical Issues to Slack",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Share Review Summary on Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Comment for GitHub": {
      "main": [
        [
          {
            "node": "Post Comment to GitHub",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Code Review with GPT-4": {
      "main": [
        [
          {
            "node": "Build Comment for GitHub",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}