AutomationFlowsAI & RAG › Review Github Pull Requests with Mistral and Send Decisions to Slack

Review Github Pull Requests with Mistral and Send Decisions to Slack

ByDave Sartori @davesartori on n8n.io

This workflow receives GitHub webhook events for pull requests, pushes, and branch/tag creates, filters out actions from the repo owner, and posts alerts to Slack (optionally Discord). For pull requests, it uses Mistral to generate an AI review from the PR diff and supports a…

Webhook trigger★★★★★ complexityAI-powered33 nodesAgentGithub ToolSlackLm Chat Mistral CloudOutput Parser StructuredGitHubDiscordHTTP Request
AI & RAG Trigger: Webhook Nodes: 33 Complexity: ★★★★★ AI nodes: yes Added:
Review Github Pull Requests with Mistral and Send Decisions to Slack — n8n workflow card showing Agent, Github Tool, Slack integration

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

This workflow follows the Agent → HTTP Request 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": "uMSp8urKBSJ50l15",
  "meta": {
    "builderVariant": "mcp",
    "aiBuilderAssisted": true,
    "templateCredsSetupCompleted": true
  },
  "name": "GitHub PR AI Review to Slack-Discord",
  "tags": [],
  "nodes": [
    {
      "id": "f9e7c0e3-e9a7-48b1-899d-d221cd7ed6e2",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        608
      ],
      "parameters": {
        "color": 7,
        "width": 592,
        "height": 352,
        "content": "## Receive and route events\n\nEntry cluster that accepts GitHub webhooks, filters unsupported events, and branches supported event types into push, create, or pull request paths."
      },
      "typeVersion": 1
    },
    {
      "id": "c0e2391d-ebf0-4661-8e69-a213e8110b7f",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 528,
        "content": "## Send push notifications\n\nExtracts repository, branch, pusher, and commit details for push events and sends the update to both Slack and Discord."
      },
      "typeVersion": 1
    },
    {
      "id": "d5f58496-a76f-462d-8eea-0c99f85b836b",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        560
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 528,
        "content": "## Send create notifications\n\nFormats create events with repository, reference, type, and creator information, then publishes the message to Slack and Discord."
      },
      "typeVersion": 1
    },
    {
      "id": "a574b68e-2894-4d71-aa91-274a7249f021",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        944,
        1120
      ],
      "parameters": {
        "color": 7,
        "width": 752,
        "height": 496,
        "content": "## Analyze pull request\n\nBuilds the PR metadata payload and runs the AI review agent. The agent uses the Mistral chat model, GitHub review lookup, PR diff retrieval, and structured output parsing to produce a review summary."
      },
      "typeVersion": 1
    },
    {
      "id": "764908ca-b28c-4733-8721-e1dd02ddc598",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1824,
        464
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 496,
        "content": "## Publish PR messages\n\nTop-right notification cluster that posts the AI-generated pull request review to Slack and Discord and later sends confirmation messages after the human review step."
      },
      "typeVersion": 1
    },
    {
      "id": "95264798-80ca-4217-a1ff-9e0c303a8943",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1824,
        992
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 352,
        "content": "## Collect review decision\n\nPresents or waits for the PR review form response and routes the selected human decision into the appropriate approval, change-request, or denial path."
      },
      "typeVersion": 1
    },
    {
      "id": "11133324-c4f7-43ba-be7e-9a71c0fe3df2",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2432,
        832
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 688,
        "content": "## Apply GitHub outcome\n\nExecutes the selected GitHub review action. Approved PRs continue to an HTTP merge request, while change requests and denials are posted as final review outcomes."
      },
      "typeVersion": 1
    },
    {
      "id": "ae80c1c2-79a4-44f7-9e0e-a73d73c9a7aa",
      "name": "When GitHub PR Created",
      "type": "n8n-nodes-base.webhook",
      "position": [
        336,
        784
      ],
      "parameters": {
        "path": "github-pr",
        "options": {
          "responseCode": {
            "values": {}
          }
        },
        "httpMethod": "POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "4701ef50-174e-498e-aea2-adbd77a06b67",
      "name": "Set PR Metadata",
      "type": "n8n-nodes-base.set",
      "position": [
        992,
        1280
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "pr-title",
              "name": "prTitle",
              "type": "string",
              "value": "={{ $json.body?.pull_request?.title ?? $json.pull_request?.title ?? '' }}"
            },
            {
              "id": "pr-body",
              "name": "prBody",
              "type": "string",
              "value": "={{ $json.body?.pull_request?.body ?? $json.pull_request?.body ?? '' }}"
            },
            {
              "id": "pr-url",
              "name": "prUrl",
              "type": "string",
              "value": "={{ $json.body?.pull_request?.html_url ?? $json.pull_request?.html_url ?? '' }}"
            },
            {
              "id": "diff-url",
              "name": "diffUrl",
              "type": "string",
              "value": "={{ $json.body?.pull_request?.diff_url ?? $json.pull_request?.diff_url ?? '' }}"
            },
            {
              "id": "pr-number",
              "name": "prNumber",
              "type": "number",
              "value": "={{ $json.body?.pull_request?.number ?? $json.pull_request?.number ?? 0 }}"
            },
            {
              "id": "repo-owner",
              "name": "repoOwner",
              "type": "string",
              "value": "={{ $json.body?.repository?.owner?.login ?? $json.repository?.owner?.login ?? '' }}"
            },
            {
              "id": "repo-name",
              "name": "repoName",
              "type": "string",
              "value": "={{ $json.body?.repository?.name ?? $json.repository?.name ?? '' }}"
            },
            {
              "id": "author",
              "name": "author",
              "type": "string",
              "value": "={{ $json.body?.pull_request?.user?.login ?? $json.pull_request?.user?.login ?? '' }}"
            },
            {
              "id": "action",
              "name": "action",
              "type": "string",
              "value": "={{ $json.body?.action ?? $json.action ?? '' }}"
            },
            {
              "id": "head-ref",
              "name": "headRef",
              "type": "string",
              "value": "={{ $json.body?.pull_request?.head?.ref ?? $json.pull_request?.head?.ref ?? '' }}"
            },
            {
              "id": "base-ref",
              "name": "baseRef",
              "type": "string",
              "value": "={{ $json.body?.pull_request?.base?.ref ?? $json.pull_request?.base?.ref ?? '' }}"
            },
            {
              "id": "additions",
              "name": "additions",
              "type": "number",
              "value": "={{ $json.body?.pull_request?.additions ?? $json.pull_request?.additions ?? 0 }}"
            },
            {
              "id": "deletions",
              "name": "deletions",
              "type": "number",
              "value": "={{ $json.body?.pull_request?.deletions ?? $json.pull_request?.deletions ?? 0 }}"
            },
            {
              "id": "changed-files",
              "name": "changedFiles",
              "type": "number",
              "value": "={{ $json.body?.pull_request?.changed_files ?? $json.pull_request?.changed_files ?? 0 }}"
            },
            {
              "id": "eea8addf-b7dc-40c9-a191-10bcb3dce617",
              "name": "headSha",
              "type": "string",
              "value": "={{ $json.body.pull_request.head.sha }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7e01a96d-9f0e-451d-bb02-095be37f68be",
      "name": "PR Summary Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1328,
        1280
      ],
      "parameters": {
        "text": "=Evaluate this pull request.\n\nREPOSITORY: {{ $json.repoOwner }}/{{ $json.repoName }}\nPR #{{ $json.prNumber }}\nTITLE: {{ $json.prTitle }}\nDESCRIPTION: {{ $json.prBody }}\nAUTHOR: {{ $json.author }}\nBRANCH: {{ $json.headRef }} \u2192 {{ $json.baseRef }}\nCHANGES: +{{ $json.additions }}/-{{ $json.deletions }} across {{ $json.changedFiles }} file(s)\nPR URL: {{ $json.prUrl }}\n\nTASK:\n1. Use **\ud83d\udd27 PR Diff Tool v2** to fetch the unified diff of this PR. Analyze what changed.\n2. If the PR action was **synchronize** (updated after prior review), also use **Get PR Reviews** to check what was previously requested. Note whether the new diff addresses those requests.\n3. Assess quality, risk, and output your review.\n\n# OUTPUT FORMAT\n\nReturn ONLY raw valid JSON. No markdown. No explanation. No preamble. No postamble. Malformed or wrapped JSON will be rejected.\n\n{\n  \"repo\": \"{{ $json.repoOwner }}/{{ $json.repoName }}\",\n  \"branch\": \"{{ $json.headRef }} \u2192 {{ $json.baseRef }}\",\n  \"pr\": \"{{ $json.prUrl }}\",\n  \"submittedBy\": \"{{ $json.author }}\",\n  \"files\": [],\n  \"recommend:\" \"approve/request changes/reject\",\n  \"summary\": \"\",\n  \"evaluation\": \"\"\n}\n\nPopulate the fields with your analysis. In \"files\", list each file changed. In \"summary\", give 1-2 sentences on the recommendation. In \"evaluation\", write 4-5 sentences on what changed, code quality, risks, why the verdict, and if this is a re-review note whether prior feedback was addressed.",
        "options": {
          "maxIterations": 10,
          "systemMessage": "You are a technical PR reviewer for Owner's GitHub. PRs and pushes may come from AI sub-agents, not just human developers. Your job is to evaluate pull request diffs and provide actionable feedback.\n\nCode comparison scope: JavaScript (.js), HTML (.html), CSS (.css), Python (.py), and configuration files.\n\nFocus areas:\n- What changed and why\n- Code quality and potential issues\n- Security considerations\n- Whether the change is safe to merge\n- If from an AI agent: extra scrutiny on logic correctness"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "4c9f0ef2-e63c-4aae-b8fd-67a8e20dd00e",
      "name": "Fetch GitHub PR Reviews",
      "type": "n8n-nodes-base.githubTool",
      "position": [
        1328,
        1440
      ],
      "parameters": {
        "limit": 20,
        "owner": "={{ $json.repoOwner }}",
        "resource": "review",
        "operation": "getAll",
        "repository": "={{ $json.repoName }}",
        "pullRequestNumber": "={{ $json.prNumber }}"
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "d77666f5-5ac7-405e-9d34-7c11135bb591",
      "name": "Post PR Summary to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1872,
        624
      ],
      "parameters": {
        "text": "=\u2699\ufe0f **GIT PR Pending** \n_____________________________\nRepo: {{ $json.output.repo }}\nBranches: {{ $json.output.branch }}\nAI Recommends: {{ $json.output.recommend }}\n\nReview please \ud83d\udc47\n<{{$execution.resumeFormUrl}}|Open PR Review Form>",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0AQ9SFQRJB",
          "cachedResultName": "n8n-mssgs"
        },
        "otherOptions": {
          "mrkdwn": true,
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "29d246b9-f05e-4ae8-9897-4505ea9185d0",
      "name": "Mistral Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatMistralCloud",
      "position": [
        1200,
        1440
      ],
      "parameters": {
        "model": "mistral-small-latest",
        "options": {}
      },
      "credentials": {
        "mistralCloudApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "36e9c26f-c2d1-46ae-a36e-241091e0a3d0",
      "name": "Parse Structured Output",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1568,
        1440
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"repo\": { \"type\": \"string\" },\n    \"branch\": { \"type\": \"string\" },\n    \"submittedBy\": { \"type\": \"string\" },\n    \"files\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } },\n    \"recommend\": { \"type\": \"string\" },\n    \"summary\": { \"type\": \"string\" },\n    \"evaluation\": { \"type\": \"string\" }\n  },\n  \"required\": [\"repo\", \"branch\", \"submittedBy\", \"files\", \"recommend\", \"summary\", \"evaluation\"]\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "8ced79a9-17a0-458b-80a9-ee4f9d858701",
      "name": "Route by Review Decision",
      "type": "n8n-nodes-base.switch",
      "position": [
        2208,
        1152
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Approve",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "493e535e-a7e8-4fcb-9cb8-4c8a4eff0226",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.Decision }}",
                    "rightValue": "Approve"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Request Changes",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "b9fb95fc-e8f5-4aa3-b587-018bef5966c0",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.Decision }}",
                    "rightValue": "Request Changes"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Deny",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "492dcd75-0bd6-402e-8f67-462e24fd7cff",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.Decision }}",
                    "rightValue": "Deny"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "932ee614-4e4d-40be-ba50-a123a20760ea",
      "name": "Approve Pull Request",
      "type": "n8n-nodes-base.github",
      "position": [
        2480,
        1008
      ],
      "parameters": {
        "owner": "={{ $('Set PR Metadata').item.json.repoOwner }}",
        "resource": "review",
        "repository": "={{ $('Set PR Metadata').item.json.repoName }}",
        "additionalFields": {},
        "pullRequestNumber": "={{ $('Set PR Metadata').item.json.prNumber }}"
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "a5d1dea3-fc4d-4d4e-b704-7be1552d837b",
      "name": "Request PR Changes",
      "type": "n8n-nodes-base.github",
      "position": [
        2480,
        1184
      ],
      "parameters": {
        "body": "={{ $json.Reason || \"Changes requested by reviewer.\" }}",
        "event": "requestChanges",
        "owner": "={{ $('Set PR Metadata').item.json.repoOwner }}",
        "resource": "review",
        "repository": "={{ $('Set PR Metadata').item.json.repoName }}",
        "additionalFields": {},
        "pullRequestNumber": "={{ $('Set PR Metadata').item.json.prNumber }}"
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "be01478e-5506-4161-bca5-03e182766b13",
      "name": "Deny Pull Request",
      "type": "n8n-nodes-base.github",
      "position": [
        2480,
        1360
      ],
      "parameters": {
        "owner": "={{ $('Set PR Metadata').item.json.repoOwner }}",
        "operation": "edit",
        "editFields": {
          "body": "PR review was not approved.",
          "state": "closed"
        },
        "repository": "={{ $('Set PR Metadata').item.json.repoName }}",
        "issueNumber": "={{ $('Set PR Metadata').item.json.prNumber }}"
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "f843f1bc-0921-4f3f-8b8c-646b8db69484",
      "name": "Wait for Review Form",
      "type": "n8n-nodes-base.wait",
      "position": [
        1872,
        1168
      ],
      "parameters": {
        "resume": "form",
        "options": {
          "respondWithOptions": {
            "values": {
              "formSubmittedText": "Your PR review has been submitted. Thank you!"
            }
          }
        },
        "formTitle": "=PR Title: \"{{ $('Set PR Metadata').item.json.prTitle }}\"",
        "formFields": {
          "values": [
            {
              "fieldType": "dropdown",
              "fieldLabel": "Decision",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Approve"
                  },
                  {
                    "option": "Request Changes"
                  },
                  {
                    "option": "Deny"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldType": "textarea",
              "fieldLabel": "Reason",
              "placeholder": "Enter detailed feedback if requesting changes..."
            }
          ]
        },
        "resumeAmount": 24,
        "limitWaitTime": true,
        "formDescription": "=<h2>PR Review</h2><p><strong>Repository:</strong> {{ $('Set PR Metadata').item.json.repoOwner }}/{{ $('Set PR Metadata').item.json.repoName }}<br><strong>Author:</strong> {{ $('Set PR Metadata').item.json.author }}<br><strong>PR #:</strong> {{ $('Set PR Metadata').item.json.prNumber }}<br><strong>AI Recommends:</strong><strong> {{ $json.output.recommend }}</strong></p><hr>\n<br>\n<h3>Summary:</h3> \n{{ $json.output.summary }}\n<br>\n<h3>Evaluation:</h3> \n{{ $json.output.evaluation }}\n"
      },
      "typeVersion": 1.1
    },
    {
      "id": "894de6a3-42a4-4e60-9e3f-e4b5e307539a",
      "name": "Set Push Event Info",
      "type": "n8n-nodes-base.set",
      "position": [
        1008,
        272
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "push-repo",
              "name": "repo",
              "type": "string",
              "value": "={{ $json.body?.repository?.full_name ?? $json.repository?.full_name ?? '' }}"
            },
            {
              "id": "push-branch",
              "name": "branch",
              "type": "string",
              "value": "={{ ($json.body?.ref ?? $json.ref ?? '').replace('refs/heads/', '') }}"
            },
            {
              "id": "push-pusher",
              "name": "pusher",
              "type": "string",
              "value": "={{ $json.body?.pusher?.name ?? $json.pusher?.name ?? $json.body?.sender?.login ?? $json.sender?.login ?? '' }}"
            },
            {
              "id": "push-count",
              "name": "commitCount",
              "type": "number",
              "value": "={{ $json.body?.commits?.length ?? $json.commits?.length ?? 0 }}"
            },
            {
              "id": "push-msg",
              "name": "latestMessage",
              "type": "string",
              "value": "={{ $json.body?.head_commit?.message ?? $json.head_commit?.message ?? $json.body?.commits?.[0]?.message ?? $json.commits?.[0]?.message ?? '' }}"
            },
            {
              "id": "push-files",
              "name": "files",
              "type": "string",
              "value": "={{ ($json.body?.commits ?? $json.commits ?? []).map(c => c.modified).flat().join(', ') || 'unknown' }}"
            },
            {
              "id": "push-deleted",
              "name": "isDeleted",
              "type": "boolean",
              "value": "={{ $json.body?.deleted ?? $json.deleted ?? false }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "c7f4f957-1eed-4a83-b0db-d6acb08976d0",
      "name": "Set Creation Event Info",
      "type": "n8n-nodes-base.set",
      "position": [
        1008,
        800
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "create-repo",
              "name": "repo",
              "type": "string",
              "value": "={{ $json.body?.repository?.full_name ?? $json.repository?.full_name ?? '' }}"
            },
            {
              "id": "create-ref",
              "name": "refName",
              "type": "string",
              "value": "={{ $json.body?.ref ?? $json.ref ?? '' }}"
            },
            {
              "id": "create-type",
              "name": "refType",
              "type": "string",
              "value": "={{ $json.body?.ref_type ?? $json.ref_type ?? '' }}"
            },
            {
              "id": "create-creator",
              "name": "creator",
              "type": "string",
              "value": "={{ $json.body?.sender?.login ?? $json.sender?.login ?? '' }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "81a301ec-6aa1-487f-81a0-bbe762ca6d3c",
      "name": "Post PR to Discord",
      "type": "n8n-nodes-base.discord",
      "disabled": true,
      "position": [
        1872,
        800
      ],
      "parameters": {
        "content": "=\u2699\ufe0f **GIT PR Pending** for: {{ $json.output.repo }}\n______________________________\nAI Recommends: {{ $json.output.recommend }}\nReview: **[Open PR Review Form]({{$execution.resumeFormUrl}})**",
        "options": {},
        "authentication": "webhook"
      },
      "credentials": {
        "discordWebhookApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "63754cbc-7055-4ea5-9e29-19a890e26d1c",
      "name": "Post Push to Discord",
      "type": "n8n-nodes-base.discord",
      "disabled": true,
      "position": [
        1232,
        368
      ],
      "parameters": {
        "content": "=\ud83d\ude80 **PUSH Detected**\n**Repository:** {{ $json.repo }}\n**Branch:** `{{ $json.branch }}`\n**Author:** {{ $json.pusher }}\n**Commits:** {{ $json.commitCount }}\n**Files:** {{ $json.files }}\n**Latest:** {{ $json.latestMessage }}",
        "options": {},
        "authentication": "webhook"
      },
      "credentials": {
        "discordWebhookApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "f36b1ec8-14bb-4d90-b1fa-3a1d50693c43",
      "name": "Post Creation to Discord",
      "type": "n8n-nodes-base.discord",
      "disabled": true,
      "position": [
        1232,
        928
      ],
      "parameters": {
        "content": "=\ud83c\udf3f **BRANCH/TAG CREATE Detected**\n**Repository:** {{ $json.repo }}\n**Branch:** `{{ $json.refName }}`\n**Type:** {{ $json.refType }}\n**By:** {{ $json.creator }}",
        "options": {},
        "authentication": "webhook"
      },
      "credentials": {
        "discordWebhookApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "4d616a37-ee49-4a89-ae92-9b60a0f700a2",
      "name": "Route by Event Type",
      "type": "n8n-nodes-base.switch",
      "position": [
        736,
        768
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "c40ab3f2-3abf-4a69-9dfb-5470a2db1621",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.headers[\"x-github-event\"] }}",
                    "rightValue": "push"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "50546fe4-efee-47f9-8524-d24b40166b5d",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.headers[\"x-github-event\"] }}",
                    "rightValue": "create"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "8d3d7d16-19a1-4341-acab-d07d67bea371",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.headers[\"x-github-event\"] }}",
                    "rightValue": "pull_request"
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "324bc830-b9a0-4eaa-b754-3523af52900f",
      "name": "Post Push to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1232,
        160
      ],
      "parameters": {
        "text": "=\ud83d\ude80 *Git Alert:* A PUSH was made to *{{ $json.repo }}*\nBRANCH: `{{ $json.branch }}`\nBy: {{ $json.pusher }}\nCommits: {{ $json.commitCount }}\nFiles: {{ $json.files }}\nLatest: {{ $json.latestMessage }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0AQ9SFQRJB",
          "cachedResultName": "n8n-mssgs"
        },
        "otherOptions": {
          "mrkdwn": true,
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "de197d6f-d0bf-4437-b51b-5df6d77ad29f",
      "name": "Post Creation to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1232,
        720
      ],
      "parameters": {
        "text": "=\ud83c\udf3f *Git Alert:* A {{ $json.refType }} was created in *{{ $json.repo }}*\nBRANCH: `{{ $json.refName }}`\nBy: {{ $json.creator }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0AQ9SFQRJB",
          "cachedResultName": "n8n-mssgs"
        },
        "otherOptions": {
          "mrkdwn": true,
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "6de7d222-d05d-40cf-860f-fb88b068c3dd",
      "name": "Confirm Discord Post",
      "type": "n8n-nodes-base.discord",
      "disabled": true,
      "position": [
        2208,
        800
      ],
      "parameters": {
        "content": "=\u2611\ufe0f {{ $json.Decision }} sent to GitHub. \nPR #{{ $('Set PR Metadata').item.json.prNumber }} - {{ $json.Decision }}",
        "options": {},
        "authentication": "webhook"
      },
      "credentials": {
        "discordWebhookApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "01f7ba37-cf3f-4600-9ebb-69ddec4d56df",
      "name": "Confirm Slack Post",
      "type": "n8n-nodes-base.slack",
      "position": [
        2208,
        624
      ],
      "parameters": {
        "text": "=\u2611\ufe0f {{ $json.Decision }} sent to GitHub. \nPR #{{ $('Set PR Metadata').item.json.prNumber }} - {{ $json.Decision}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0AQ9SFQRJB",
          "cachedResultName": "n8n-mssgs"
        },
        "otherOptions": {
          "mrkdwn": true,
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "b6901891-fe45-48b3-971f-5c787b326097",
      "name": "Filter GitHub Events",
      "type": "n8n-nodes-base.filter",
      "position": [
        544,
        784
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "cond-push-cross",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.headers[\"x-github-event\"] == \"push\" && $json.body?.deleted != true && $json.body?.sender?.login != $json.body?.repository?.owner?.login }}",
              "rightValue": true
            },
            {
              "id": "cond-create-cross",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.headers[\"x-github-event\"] == \"create\" && $json.body?.sender?.login != $json.body?.repository?.owner?.login }}",
              "rightValue": true
            },
            {
              "id": "cond-pr-cross",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.headers['x-github-event'] == 'pull_request' && ($json.body?.action == 'opened' || $json.body?.action == 'synchronize' || $json.body?.action == 'reopened') && $json.body?.pull_request?.user?.login != $json.body?.repository?.owner?.login }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "0506403f-2182-4cfe-922c-7c9a8c0ba1cb",
      "name": "Merge Pull Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2736,
        1008
      ],
      "parameters": {
        "url": "=https://api.github.com/repos/{{ $('Set PR Metadata').item.json.repoOwner }}/{{ $('Set PR Metadata').item.json.repoName }}/pulls/{{ $('Set PR Metadata').item.json.prNumber }}/merge",
        "method": "PUT",
        "options": {},
        "sendBody": true,
        "authentication": "genericCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "merge_method",
              "value": "merge"
            },
            {
              "name": "sha",
              "value": "={{ $('Set PR Metadata').item.json.headSha }}"
            }
          ]
        },
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpBearerAuth": {
          "name": "<your credential>"
        },
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "e06ae9ca-85ec-4c20-996f-73444b35a1cd",
      "name": "Get PR Diff Data",
      "type": "n8n-nodes-base.httpRequestTool",
      "notes": "Working HTTP Request Tool for AI to fetch PR diffs. Replaces broken @n8n/n8n-nodes-langchain.toolHttpRequest.",
      "position": [
        1440,
        1440
      ],
      "parameters": {
        "url": "={{ $json.diffUrl }}",
        "options": {}
      },
      "typeVersion": 4.4
    },
    {
      "id": "93fa01ee-5e24-4b59-96db-3bcb77c59be4",
      "name": "Setup & Summary",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -336,
        32
      ],
      "parameters": {
        "width": 600,
        "height": 1736,
        "content": "# GitHub PR AI Review \u2192 Slack/Discord\n\n### How it works\nOne GitHub webhook catches PRs, pushes, and branch/tag creates. Events are filtered for \"contributor \u2192 owner\" \u2014 the repo owner's own actions get silently dropped.\n\nPRs get an AI review (the LLM reads the actual diff and checks prior reviews if it's a re-review), then a human review (HITL) form is sent to Slack/Discord. The owner's decision flows back to GitHub \u2014 approve and it merges, request changes and the bot can fix and re-push. Push and branch-create events just send a quick alert. MS Teams could easily be added.\n\n**Flow:** Webhook \u2192 Filter (push/create/PR from other accounts only) \u2192 Route by event type\n- **PR** \u2192 Extract Metadata \u2192 AI Agent (Mistral + PR Diff Tool + Get PR Reviews) \u2192 Slack/Discord with form link + Review Form \u2192 Route Decision (Approve / Request Changes / Deny) \u2192 GitHub action + Merge \u2611\ufe0f\n- When the bot pushes an update to an open PR, it triggers a **synchronize** event \u2014 the AI re-reviews the new diff and checks prior reviews, noting whether feedback was addressed. Fresh form each time.\n- **Push** \u2192 Extract Info (repo, branch, who, commits, files) \u2192 Slack/Discord\n- **Create** \u2192 Extract Info (repo, ref name, type, who) \u2192 Slack/Discord\n\n### Setup steps\n- **GitHub Webhook**: Repo \u2192 Settings \u2192 Webhooks \u2192 Add webhook. Payload URL: `https://your-n8n-domain.com/webhook/github`. Content type: `application/json`. Events: **pull_request**, **push**, **create** (select individually).\n- **Slack**: create a Slack App \u2192 OAuth & Permissions \u2192 add `chat:write` scope \u2192 install to workspace \u2192 use Bot Token in n8n Slack credential.\n- **Discord**: Channel \u2192 Integrations \u2192 Webhooks \u2192 Create webhook \u2192 paste URL into n8n Discord Webhook credential.\n- **Teams** (enterprise): Channel \u2192 Connectors \u2192 Incoming Webhook \u2014 same pattern, use an HTTP Request node. Toggle whichever platform you want active by enabling/disabling its node group.\n- **GitHub Credentials**: an Owner PAT (`repo` scope) drives the GitHub Tool and action nodes; a separate Bot PAT is needed because GitHub won't let you approve your own PR. The Merge PR node needs a Header Auth credential (Name: `Authorization`, Value: `Bearer <PAT>`).\n- **Mistral Cloud** credentials for the AI chat model; verify the PR Diff Tool can reach the diff URL.\n- Requirements: two GitHub accounts to test the full flow (one authors, one reviews), a self-hosted n8n reachable from the internet (Cloudflare Tunnel works great), review form times out after 24h.\n\n### \ud83e\uddea How to test\n- Generate a classic PAT for each account (Settings \u2192 Developer settings \u2192 Tokens).\n- Bot clones via `git clone https://BOT_TOKEN@github.com/Owner/repo.git`, edits, commits, pushes.\n- Create the PR via curl: `curl -X POST -H \"Authorization: token BOT_TOKEN\" https://api.github.com/repos/Owner/repo/pulls -d '{\"title\":\"...\",\"head\":\"BotOwner:branch\",\"base\":\"main\"}'`\n- n8n receives the webhook \u2192 AI review \u2192 form \u2192 approve/deny.\n- **Why the same-author error?** GitHub blocks self-approval on `review > create` \u2014 the PR author must differ from the reviewer. Bot authors, Owner reviews, separate tokens.\n- **No-token alternative**: do it all on the GitHub web UI \u2014 bot forks, edits, and opens the PR there.\n\n### Customization\nAdjust the AI agent's prompt and structured-output schema to change review criteria, risk scoring, or summary format. Update Slack/Discord message templates, target channels, and merge behavior to match your team's policy. Adjust the switch conditions in the event router and decision router if your GitHub payload fields or form values differ.\n\ncontact: dev@w57th.agency\n@[youtube](l-14nr4rhQk)"
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": true,
    "executionOrder": "v1"
  },
  "versionId": "ec329190-eeef-41cf-b517-41fcfb36f0ca",
  "nodeGroups": [],
  "connections": {
    "Set PR Metadata": {
      "main": [
        [
          {
            "node": "PR Summary Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get PR Diff Data": {
      "ai_tool": [
        [
          {
            "node": "PR Summary Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "PR Summary Agent": {
      "main": [
        [
          {
            "node": "Post PR Summary to Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Wait for Review Form",
            "type": "main",
            "index": 0
          },
          {
            "node": "Post PR to Discord",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mistral Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "PR Summary Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Route by Event Type": {
      "main": [
        [
          {
            "node": "Set Push Event Info",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set Creation Event Info",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set PR Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Push Event Info": {
      "main": [
        [
          {
            "node": "Post Push to Discord",
            "type": "main",
            "index": 0
          },
          {
            "node": "Post Push to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approve Pull Request": {
      "main": [
        [
          {
            "node": "Merge Pull Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter GitHub Events": {
      "main": [
        [
          {
            "node": "Route by Event Type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Review Form": {
      "main": [
        [
          {
            "node": "Route by Review Decision",
            "type": "main",
            "index": 0
          },
          {
            "node": "Confirm Discord Post",
            "type": "main",
            "index": 0
          },
          {
            "node": "Confirm Slack Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When GitHub PR Created": {
      "main": [
        [
          {
            "node": "Filter GitHub Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch GitHub PR Reviews": {
      "ai_tool": [
        [
          {
            "node": "PR Summary Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Parse Structured Output": {
      "ai_outputParser": [
        [
          {
            "node": "PR Summary Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Set Creation Event Info": {
      "main": [
        [
          {
            "node": "Post Creation to Discord",
            "type": "main",
            "index": 0
          },
          {
            "node": "Post Creation to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Review Decision": {
      "main": [
        [
          {
            "node": "Approve Pull Request",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Request PR Changes",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Deny Pull Request",
            "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

This workflow receives GitHub webhook events for pull requests, pushes, and branch/tag creates, filters out actions from the repo owner, and posts alerts to Slack (optionally Discord). For pull requests, it uses Mistral to generate an AI review from the PR diff and supports a…

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

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

This workflow automatically processes new free-trial / lead sign-ups in real time: Catches a webhook from any source (Webflow form, Intercom, custom agent, etc.) Filters out personal / disposable / .e

Output Parser Structured, Agent, HTTP Request +7
AI & RAG

This workflow listens for GitHub pull-request events, analyzes changed React/TypeScript files, auto-generates Jest tests via AI, has them reviewed by a second AI pass, and posts suggestions back as PR

GitHub, HTTP Request, Github Tool +3
AI & RAG

N8N-Tutorial-Final. Uses lmChatOpenAi, httpRequest, httpRequestTool, github. Webhook trigger; 17 nodes.

OpenAI Chat, HTTP Request, HTTP Request Tool +5
AI & RAG

Who is this for? Agencies, consultants, and service providers who conduct discovery calls and need to quickly turn conversations into professional proposals.

Tool Think, Tool Calculator, Agent Tool +18
AI & RAG

Fully automates your service order pipeline from incoming booking to supplier confirmation — with built-in SLA enforcement and automatic escalation if a supplier goes silent. 📥 Receives orders via web

HTTP Request, Google Sheets, Agent +4