AutomationFlowsGeneral › GitHub PR Auto Fix with Gemini Review

GitHub PR Auto Fix with Gemini Review

Original n8n title: Jump-section: Auto Fix Pipeline

jump-section: Auto Fix Pipeline. Uses httpRequest. Webhook trigger; 16 nodes.

Webhook trigger★★★★☆ complexity16 nodesHTTP Request
General Trigger: Webhook Nodes: 16 Complexity: ★★★★☆ Added:

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
{
  "name": "jump-section: Auto Fix Pipeline",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "pr-auto-fix",
        "responseMode": "onReceived",
        "responseData": "noData"
      },
      "id": "webhook-trigger",
      "name": "Webhook: PR Review Event",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false
          },
          "conditions": [
            {
              "id": "action-check",
              "leftValue": "={{ $json.body.action }}",
              "rightValue": "submitted",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "gemini-check",
              "leftValue": "={{ $json.body.review.user.login }}",
              "rightValue": "gemini-code-assist[bot]",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "check-gemini-review",
      "name": "IF: Gemini \ub9ac\ubdf0\uc778\uc9c0 \ud655\uc778",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const body = $input.first().json.body;\nconst pr = body.pull_request;\nconst review = body.review;\nconst repo = body.repository;\n\nreturn [{\n  json: {\n    prNumber: pr.number,\n    prTitle: pr.title,\n    prUrl: pr.html_url,\n    headBranch: pr.head.ref,\n    headSha: pr.head.sha,\n    repoOwner: repo.owner.login,\n    repoName: repo.name,\n    reviewBody: review.body || '',\n    reviewState: review.state,\n    reviewId: review.id\n  }\n}];"
      },
      "id": "extract-info",
      "name": "PR \uc815\ubcf4 \ucd94\ucd9c",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.github.com/repos/{{ $json.repoOwner }}/{{ $json.repoName }}/pulls/{{ $json.prNumber }}/comments",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            },
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.GITHUB_TOKEN }}"
            }
          ]
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "per_page",
              "value": "50"
            }
          ]
        },
        "options": {}
      },
      "id": "get-review-comments",
      "name": "GitHub: \ub9ac\ubdf0 \uc778\ub77c\uc778 \ucf54\uba58\ud2b8 \uc870\ud68c",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        900,
        200
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.github.com/repos/{{ $('PR \uc815\ubcf4 \ucd94\ucd9c').first().json.repoOwner }}/{{ $('PR \uc815\ubcf4 \ucd94\ucd9c').first().json.repoName }}/pulls/{{ $('PR \uc815\ubcf4 \ucd94\ucd9c').first().json.prNumber }}/files",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            },
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.GITHUB_TOKEN }}"
            }
          ]
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "per_page",
              "value": "30"
            }
          ]
        },
        "options": {}
      },
      "id": "get-pr-files",
      "name": "GitHub: PR \ubcc0\uacbd \ud30c\uc77c \uc870\ud68c",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        900,
        400
      ]
    },
    {
      "parameters": {
        "jsCode": "const prInfo = $('PR \uc815\ubcf4 \ucd94\ucd9c').first().json;\nconst inlineComments = $('GitHub: \ub9ac\ubdf0 \uc778\ub77c\uc778 \ucf54\uba58\ud2b8 \uc870\ud68c').all().map(i => i.json);\nconst prFiles = $input.all().map(f => f.json);\n\n// Gemini \uc778\ub77c\uc778 \ucf54\uba58\ud2b8 \ud544\ud130\ub9c1\nconst geminiComments = inlineComments.filter(c =>\n  c.user && c.user.login === 'gemini-code-assist[bot]'\n);\n\n// \ucf54\uba58\ud2b8 \ud14d\uc2a4\ud2b8 \uc815\ub9ac\nconst commentsText = geminiComments.length > 0\n  ? geminiComments.map(c => `\ud83d\udccd ${c.path} (line ${c.original_line || c.line || '?'}):\\n${c.body}`).join('\\n\\n')\n  : '(\uc778\ub77c\uc778 \ucf54\uba58\ud2b8 \uc5c6\uc74c)';\n\n// \ubcc0\uacbd \ud30c\uc77c \ubaa9\ub85d\nconst filePaths = prFiles.map(f => f.filename);\n\n// \ub9ac\ubdf0 \uc804\uccb4 \uc694\uc57d\nconst reviewSummary = [\n  `## Gemini \ub9ac\ubdf0 \uc694\uc57d\\n${prInfo.reviewBody || '(\uc5c6\uc74c)'}`,\n  `## \uc778\ub77c\uc778 \ucf54\uba58\ud2b8\\n${commentsText}`\n].join('\\n\\n');\n\nreturn [{\n  json: {\n    ...prInfo,\n    reviewSummary,\n    filePaths,\n    hasComments: geminiComments.length > 0 || prInfo.reviewBody.length > 0\n  }\n}];"
      },
      "id": "prepare-context",
      "name": "\ub9ac\ubdf0 \ucee8\ud14d\uc2a4\ud2b8 \uc900\ube44",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "conditions": [
            {
              "id": "has-comments",
              "leftValue": "={{ $json.hasComments }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ]
        }
      },
      "id": "check-has-comments",
      "name": "IF: \uc218\uc815\ud560 \ucf54\uba58\ud2b8 \uc788\ub294\uc9c0 \ud655\uc778",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1340,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// \ubcc0\uacbd\ub41c \ud30c\uc77c\ub4e4\uc744 \uc77d\uae30 \uc704\ud574 \uac01 \ud30c\uc77c \uacbd\ub85c\ub97c \uac1c\ubcc4 \uc544\uc774\ud15c\uc73c\ub85c \ubd84\ub9ac\nconst ctx = $input.first().json;\nreturn ctx.filePaths.map(path => ({\n  json: {\n    path,\n    branch: ctx.headBranch,\n    repoOwner: ctx.repoOwner,\n    repoName: ctx.repoName,\n    // \uc804\uccb4 \ucee8\ud14d\uc2a4\ud2b8\ub3c4 \ud568\uaed8 \uc804\ub2ec\n    _ctx: ctx\n  }\n}));"
      },
      "id": "split-files",
      "name": "\ud30c\uc77c \ubaa9\ub85d \ubd84\ub9ac",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1560,
        300
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.github.com/repos/{{ $json.repoOwner }}/{{ $json.repoName }}/contents/{{ $json.path }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            },
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.GITHUB_TOKEN }}"
            }
          ]
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "ref",
              "value": "={{ $json.branch }}"
            }
          ]
        },
        "options": {}
      },
      "id": "read-files",
      "name": "GitHub: \ud30c\uc77c \ub0b4\uc6a9 \uc77d\uae30",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1780,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const fileItems = $('GitHub: \ud30c\uc77c \ub0b4\uc6a9 \uc77d\uae30').all();\nconst splitItems = $('\ud30c\uc77c \ubaa9\ub85d \ubd84\ub9ac').all();\nconst ctx = splitItems[0].json._ctx;\n\nconst files = fileItems.map((item, i) => {\n  const content = Buffer.from((item.json.content || '').replace(/\\n/g, ''), 'base64').toString('utf8');\n  return {\n    path: item.json.path,\n    content,\n    sha: item.json.sha\n  };\n});\n\nconst filesContext = files.map(f =>\n  `### ${f.path}\\n\\`\\`\\`typescript\\n${f.content}\\n\\`\\`\\``\n).join('\\n\\n');\n\n// CI \uc2e4\ud328 \uc815\ubcf4 \uc870\ud68c\nconst token = $env.GITHUB_TOKEN;\nfunction ghGet(path) {\n  return new Promise((resolve, reject) => {\n    const https = require('https');\n    const req = https.request({\n      hostname: 'api.github.com',\n      path,\n      method: 'GET',\n      headers: {\n        'Authorization': `Bearer ${token}`,\n        'Accept': 'application/vnd.github+json',\n        'X-GitHub-Api-Version': '2022-11-28',\n        'User-Agent': 'n8n-jump-section'\n      }\n    }, (res) => {\n      let data = '';\n      res.on('data', chunk => data += chunk);\n      res.on('end', () => {\n        if (res.statusCode >= 400) reject(new Error(`HTTP ${res.statusCode}: ${data}`));\n        else resolve(JSON.parse(data));\n      });\n    });\n    req.setTimeout(15000, () => { req.destroy(); reject(new Error('Request timeout')); });\n    req.on('error', reject);\n    req.end();\n  });\n}\n\nlet ciSection = '';\ntry {\n  const checkData = await ghGet(`/repos/${ctx.repoOwner}/${ctx.repoName}/commits/${ctx.headSha}/check-runs?per_page=30`);\n  const failed = (checkData.check_runs || []).filter(c =>\n    c.conclusion === 'failure' || c.conclusion === 'timed_out'\n  );\n  if (failed.length > 0) {\n    const details = failed.map(c => {\n      const lines = [`### \u274c ${c.name}`];\n      if (c.output?.summary) lines.push(c.output.summary);\n      if (c.output?.text) lines.push(c.output.text.slice(0, 8000));\n      return lines.join('\\n');\n    }).join('\\n\\n');\n    ciSection = `## CI \uc2e4\ud328 \uc815\ubcf4\\n\\n${details}\\n\\n`;\n  }\n} catch (e) {\n  ciSection = `## CI \uc2e4\ud328 \uc815\ubcf4\\n\\n(\uc870\ud68c \uc2e4\ud328: ${e.message})\\n\\n`;\n}\n\nconst userPrompt = `## PR #${ctx.prNumber}: ${ctx.prTitle}\\n\ube0c\ub79c\uce58: \\`${ctx.headBranch}\\`\\n\\n## Gemini \ub9ac\ubdf0\\n${ctx.reviewSummary}\\n\\n${ciSection}---\\n\\n## \ud604\uc7ac \ucf54\ub4dc\\n\\n${filesContext}\\n\\n---\\n\\nGemini \ub9ac\ubdf0\ub97c \ubc18\uc601\ud558\uace0, CI \uc2e4\ud328\uac00 \uc788\ub2e4\uba74 \uc6d0\uc778\uc744 \ud30c\uc545\ud558\uc5ec \ud568\uaed8 \uc218\uc815\ud558\uc138\uc694.\\n\\n**\uc911\uc694**: CI \uc5d0\ub7ec \ubaa9\ub85d\uc5d0 \ubcf4\uc774\ub294 \ubaa8\ub4e0 \uc5d0\ub7ec\ub97c \uc774\ubc88 \ud55c \ubc88\uc5d0 \uc804\ubd80 \uc218\uc815\ud558\uc138\uc694. \ud558\ub098\ub9cc \uace0\uce58\uace0 \ub05d\ub0b4\uc9c0 \ub9c8\uc138\uc694. \uac01 \uc5d0\ub7ec\ub97c \ubd84\uc11d\ud574\uc11c \uc5f0\uad00\ub41c \ud30c\uc77c\uc744 \ubaa8\ub450 write_file\ub85c \uc81c\ucd9c\ud558\uc138\uc694. \ud0c0\ub2f9\ud55c \uc9c0\uc801\ub9cc \ubc18\uc601\ud558\uace0, \uc218\uc815\uc774 \ud544\uc694\ud55c \ubaa8\ub4e0 \ud30c\uc77c\uc744 write_file\ub85c \uc81c\ucd9c\ud558\uc138\uc694.`;\n\nconst geminiPayload = {\n  system_instruction: {\n    parts: [{ text: 'TypeScript \uc804\ubb38 \uac1c\ubc1c\uc790\uc785\ub2c8\ub2e4. Gemini\uc758 \ucf54\ub4dc \ub9ac\ubdf0 \ucf54\uba58\ud2b8\uc640 CI \uc2e4\ud328 \uc815\ubcf4\ub97c \ubc14\ud0d5\uc73c\ub85c \ucf54\ub4dc\ub97c \uc218\uc815\ud569\ub2c8\ub2e4.\\n\\n## \ud544\uc218 \uaddc\uce59\\n\\n### Prettier \ud3ec\ub9f7 (\ubc18\ub4dc\uc2dc \uc900\uc218 \u2014 format:check CI\uac00 \ub3cc\uace0 \uc788\uc74c)\\n- \ub4e4\uc5ec\uc4f0\uae30: \uc2a4\ud398\uc774\uc2a4 2\uce78\\n- \uc138\ubbf8\ucf5c\ub860: \ud56d\uc0c1 \ubd99\uc784\\n- \ub530\uc634\ud45c: \uc2f1\uae00\ucffc\ud2b8 (\\'\\')\\n- \ud55c \uc904 \ucd5c\ub300 \uae38\uc774: 100\uc790\\n- trailing comma: \ud56d\uc0c1 \ubd99\uc784 (\ud568\uc218 \uc778\uc790 \ub9c8\uc9c0\ub9c9 \ud3ec\ud568)\\n- \uc904\ubc14\uafc8: LF (\\\\n)\\n\\n### \ucf54\ub4dc \uc218\uc815\\n- Gemini \ucf54\uba58\ud2b8\uc5d0\uc11c \ud0c0\ub2f9\ud55c \uc9c0\uc801\ub9cc \uc218\uc815\\n- CI \uc2e4\ud328\uac00 \uc788\uc73c\uba74 \ubc18\ub4dc\uc2dc \uc6d0\uc778\uc744 \ud30c\uc545\ud558\uace0 **\ubaa8\ub4e0 \uc5d0\ub7ec\ub97c \ud55c \ubc88\uc5d0** \uc218\uc815 (format check, type error, test failure, build error \ub4f1) \u2014 \uc77c\ubd80\ub9cc \uc218\uc815\ud558\uace0 \ub05d\ub0b4\uc9c0 \ub9d0 \uac83\\n- \uae30\uc874 Public API \ud558\uc704 \ud638\ud658\uc131 \uc720\uc9c0\\n- \uc218\uc815\uc774 \ud544\uc694 \uc5c6\ub294 \ud30c\uc77c\uc740 write_file \ud638\ucd9c \uc0dd\ub7b5\\n- \uc6d0\uc778\uc774 \uba85\ud655\ud558\uace0 \uc218\uc815 \ubc29\ud5a5\uc774 \ud655\uc2e4\ud55c \uacbd\uc6b0\uc5d0\ub9cc write_file\ub85c \uc81c\ucd9c\ud558\uc138\uc694. \uc6d0\uc778\uc774 \ubd88\ubd84\uba85\ud558\uac70\ub098 \uc758\ub3c4\uce58 \uc54a\uc740 \ub3d9\uc791 \ubcc0\uacbd \uac00\ub2a5\uc131\uc774 \uc788\uc73c\uba74 \ud574\ub2f9 \ud30c\uc77c\uc740 \uac74\ub108\ub701\ub2c8\ub2e4.' }]\n  },\n  contents: [{ role: 'user', parts: [{ text: userPrompt }] }],\n  tools: [{\n    functionDeclarations: [{\n      name: 'write_file',\n      description: '\uc218\uc815\ub41c \ud30c\uc77c\uc758 \uc804\uccb4 \ub0b4\uc6a9\uc744 \uc81c\ucd9c\ud569\ub2c8\ub2e4.',\n      parameters: {\n        type: 'OBJECT',\n        properties: {\n          path: { type: 'STRING', description: '\ud30c\uc77c \uacbd\ub85c' },\n          content: { type: 'STRING', description: '\ud30c\uc77c \uc804\uccb4 \ub0b4\uc6a9' },\n          description: { type: 'STRING', description: '\ubcc0\uacbd \uc124\uba85' }\n        },\n        required: ['path', 'content', 'description']\n      }\n    }]\n  }],\n  toolConfig: { functionCallingConfig: { mode: 'ANY' } },\n  generationConfig: { maxOutputTokens: 16384 }\n};\n\nreturn [{\n  json: {\n    ...ctx,\n    files,\n    hasCiFailures: ciSection !== '',\n    geminiPayload\n  }\n}];"
      },
      "id": "decode-files",
      "name": "\ud30c\uc77c \ub0b4\uc6a9 \ub514\ucf54\ub529",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2000,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={{ $env.GEMINI_API_KEY }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.geminiPayload }}",
        "options": {}
      },
      "id": "gemini-fix",
      "name": "Gemini: \ucf54\ub4dc \uc218\uc815",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2220,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const response = $input.first().json;\nconst ctx = $('\ud30c\uc77c \ub0b4\uc6a9 \ub514\ucf54\ub529').first().json;\n\nif (!response.candidates || response.candidates.length === 0) {\n  return [{ json: { noChanges: true, geminiError: response.promptFeedback || response.error || 'no candidates', ...ctx } }];\n}\n\nconst candidate = response.candidates[0];\nif (!candidate.content || !candidate.content.parts || candidate.finishReason === 'MALFORMED_FUNCTION_CALL') {\n  return [{ json: { noChanges: true, geminiError: candidate.finishReason || 'no content', ...ctx } }];\n}\n\nconst parts = candidate.content.parts;\nconst functionCalls = parts.filter(p => p.functionCall && p.functionCall.name === 'write_file');\n\nif (functionCalls.length === 0) {\n  // \uc218\uc815 \ud544\uc694 \uc5c6\uc74c\n  return [{ json: { noChanges: true, ...ctx } }];\n}\n\nconst fixedFiles = functionCalls.map(p => {\n  const original = ctx.files.find(f => f.path === p.functionCall.args.path);\n  return {\n    path: p.functionCall.args.path,\n    content: p.functionCall.args.content,\n    description: p.functionCall.args.description,\n    sha: original?.sha || null\n  };\n});\n\nconst filesSummary = fixedFiles.map(f => `\u2022 \\`${f.path}\\`: ${f.description}`).join('\\n');\n\nreturn [{\n  json: {\n    noChanges: false,\n    fixedFiles,\n    filesSummary,\n    prNumber: ctx.prNumber,\n    prTitle: ctx.prTitle,\n    prUrl: ctx.prUrl,\n    headBranch: ctx.headBranch,\n    repoOwner: ctx.repoOwner,\n    repoName: ctx.repoName\n  }\n}];"
      },
      "id": "parse-fix",
      "name": "\uc218\uc815 \uacb0\uacfc \ud30c\uc2f1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2440,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "conditions": [
            {
              "id": "has-changes",
              "leftValue": "={{ $json.noChanges }}",
              "rightValue": false,
              "operator": {
                "type": "boolean",
                "operation": "false"
              }
            }
          ]
        }
      },
      "id": "check-has-changes",
      "name": "IF: \uc218\uc815\ub41c \ud30c\uc77c \uc788\ub294\uc9c0 \ud655\uc778",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2660,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json;\nif (data.noChanges || !data.fixedFiles || data.fixedFiles.length === 0) return [];\nconst fixedFiles = data.fixedFiles.filter(f => f.path && f.content && f.content.trim().length > 0);\nif (fixedFiles.length === 0) return [];\nconst { headBranch, repoOwner, repoName, filesSummary } = data;\nconst token = $env.GITHUB_TOKEN;\n\nconst headers = {\n  'Authorization': `Bearer ${token}`,\n  'Accept': 'application/vnd.github+json',\n  'X-GitHub-Api-Version': '2022-11-28',\n  'Content-Type': 'application/json',\n  'User-Agent': 'n8n-jump-section'\n};\n\nconst baseUrl = `https://api.github.com/repos/${repoOwner}/${repoName}`;\n\nfunction request(method, url, body) {\n  return new Promise((resolve, reject) => {\n    const https = require('https');\n    const path = url.replace('https://api.github.com', '');\n    const options = { hostname: 'api.github.com', path, method, headers };\n    const req = https.request(options, (res) => {\n      let d = '';\n      res.on('data', chunk => d += chunk);\n      res.on('end', () => {\n        if (res.statusCode >= 400) reject(new Error(`HTTP ${res.statusCode}: ${d}`));\n        else resolve(JSON.parse(d));\n      });\n    });\n    req.setTimeout(15000, () => { req.destroy(); reject(new Error('Request timeout')); });\n    req.on('error', reject);\n    if (body) req.write(JSON.stringify(body));\n    req.end();\n  });\n}\n\n// 1. HEAD SHA\nconst refData = await request('GET', `${baseUrl}/git/refs/heads/${headBranch}`);\nconst baseSha = refData.object.sha;\n\n// 2. \ud604\uc7ac \ud2b8\ub9ac SHA\nconst commitData = await request('GET', `${baseUrl}/git/commits/${baseSha}`);\nconst baseTreeSha = commitData.tree.sha;\n\n// 3. \uc0c8 \ud2b8\ub9ac \uc0dd\uc131 (\ubaa8\ub4e0 \ud30c\uc77c \ud55c\ubc88\uc5d0)\nconst newTree = await request('POST', `${baseUrl}/git/trees`, {\n  base_tree: baseTreeSha,\n  tree: fixedFiles.map(f => ({ path: f.path, mode: '100644', type: 'blob', content: f.content }))\n});\n\n// 4. \ucee4\ubc0b \uc0dd\uc131\nconst newCommit = await request('POST', `${baseUrl}/git/commits`, {\n  message: `fix: apply Gemini review suggestions\\n\\n${filesSummary}`,\n  tree: newTree.sha,\n  parents: [baseSha]\n});\n\n// 5. \ube0c\ub79c\uce58 ref \uc5c5\ub370\uc774\ud2b8\nawait request('PATCH', `${baseUrl}/git/refs/heads/${headBranch}`, { sha: newCommit.sha });\n\nreturn [{ json: { ...data, commitSha: newCommit.sha } }];"
      },
      "id": "commit-fixes",
      "name": "GitHub: Trees API \ucee4\ubc0b",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2880,
        200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.github.com/repos/{{ $('\uc218\uc815 \uacb0\uacfc \ud30c\uc2f1').first().json.repoOwner }}/{{ $('\uc218\uc815 \uacb0\uacfc \ud30c\uc2f1').first().json.repoName }}/issues/{{ $('\uc218\uc815 \uacb0\uacfc \ud30c\uc2f1').first().json.prNumber }}/comments",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.GITHUB_TOKEN }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"body\": \"## \ud83e\udd16 AI \uc790\ub3d9 \uc218\uc815 \uc644\ub8cc\\n\\nGemini \ub9ac\ubdf0 \ucf54\uba58\ud2b8\ub97c \ubc18\uc601\ud574\uc11c \ucf54\ub4dc\ub97c \uc218\uc815\ud588\uc2b5\ub2c8\ub2e4.\\n\\n### \uc218\uc815\ub41c \ud30c\uc77c\\n{{ $('\uc218\uc815 \uacb0\uacfc \ud30c\uc2f1').first().json.filesSummary }}\\n\\n---\\n*Gemini AI\uac00 \ub9ac\ubdf0\ub97c \ubd84\uc11d\ud558\uc5ec \uc790\ub3d9 \uc218\uc815\ud588\uc2b5\ub2c8\ub2e4. \ubcc0\uacbd\uc0ac\ud56d\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.*\"\n}",
        "options": {}
      },
      "id": "post-pr-comment",
      "name": "GitHub: PR \ucf54\uba58\ud2b8 \uac8c\uc2dc",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3100,
        400
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.DISCORD_WEBHOOK_URL }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"embeds\": [\n    {\n      \"title\": \"{{ $('\ud30c\uc77c \ub0b4\uc6a9 \ub514\ucf54\ub529').first().json.hasCiFailures ? '\ud83d\udd27 Gemini \ub9ac\ubdf0 + CI \uc2e4\ud328 \uc218\uc815 \uc644\ub8cc' : '\ud83d\udd27 Gemini \ub9ac\ubdf0 \uae30\ubc18 \uc790\ub3d9 \uc218\uc815 \uc644\ub8cc' }}\",\n      \"description\": \"[PR #{{ $('\uc218\uc815 \uacb0\uacfc \ud30c\uc2f1').first().json.prNumber }}: {{ $('\uc218\uc815 \uacb0\uacfc \ud30c\uc2f1').first().json.prTitle }}]({{ $('\uc218\uc815 \uacb0\uacfc \ud30c\uc2f1').first().json.prUrl }})\",\n      \"color\": 15105570,\n      \"fields\": [\n        {\n          \"name\": \"\uc218\uc815\ub41c \ud30c\uc77c\",\n          \"value\": \"{{ $('\uc218\uc815 \uacb0\uacfc \ud30c\uc2f1').first().json.filesSummary }}\",\n          \"inline\": false\n        },\n        {\n          \"name\": \"\ube0c\ub79c\uce58\",\n          \"value\": \"`{{ $('\uc218\uc815 \uacb0\uacfc \ud30c\uc2f1').first().json.headBranch }}`\",\n          \"inline\": true\n        },\n        {\n          \"name\": \"CI \uc2e4\ud328 \ud3ec\ud568\",\n          \"value\": \"{{ $('\ud30c\uc77c \ub0b4\uc6a9 \ub514\ucf54\ub529').first().json.hasCiFailures ? '\u2705 CI \uc2e4\ud328 \uc815\ubcf4 \ubc18\uc601\ub428' : '\uc5c6\uc74c' }}\",\n          \"inline\": true\n        }\n      ],\n      \"footer\": { \"text\": \"jump-section AI Pipeline \u2022 Gemini \uc790\ub3d9 \uc218\uc815\" },\n      \"timestamp\": \"{{ new Date().toISOString() }}\"\n    }\n  ]\n}",
        "options": {}
      },
      "id": "discord-fix",
      "name": "Discord: \uc790\ub3d9 \uc218\uc815 \uc54c\ub9bc",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3320,
        300
      ]
    }
  ],
  "connections": {
    "Webhook: PR Review Event": {
      "main": [
        [
          {
            "node": "IF: Gemini \ub9ac\ubdf0\uc778\uc9c0 \ud655\uc778",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: Gemini \ub9ac\ubdf0\uc778\uc9c0 \ud655\uc778": {
      "main": [
        [
          {
            "node": "PR \uc815\ubcf4 \ucd94\ucd9c",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PR \uc815\ubcf4 \ucd94\ucd9c": {
      "main": [
        [
          {
            "node": "GitHub: \ub9ac\ubdf0 \uc778\ub77c\uc778 \ucf54\uba58\ud2b8 \uc870\ud68c",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub: \ub9ac\ubdf0 \uc778\ub77c\uc778 \ucf54\uba58\ud2b8 \uc870\ud68c": {
      "main": [
        [
          {
            "node": "GitHub: PR \ubcc0\uacbd \ud30c\uc77c \uc870\ud68c",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub: PR \ubcc0\uacbd \ud30c\uc77c \uc870\ud68c": {
      "main": [
        [
          {
            "node": "\ub9ac\ubdf0 \ucee8\ud14d\uc2a4\ud2b8 \uc900\ube44",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ub9ac\ubdf0 \ucee8\ud14d\uc2a4\ud2b8 \uc900\ube44": {
      "main": [
        [
          {
            "node": "IF: \uc218\uc815\ud560 \ucf54\uba58\ud2b8 \uc788\ub294\uc9c0 \ud655\uc778",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: \uc218\uc815\ud560 \ucf54\uba58\ud2b8 \uc788\ub294\uc9c0 \ud655\uc778": {
      "main": [
        [
          {
            "node": "\ud30c\uc77c \ubaa9\ub85d \ubd84\ub9ac",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud30c\uc77c \ubaa9\ub85d \ubd84\ub9ac": {
      "main": [
        [
          {
            "node": "GitHub: \ud30c\uc77c \ub0b4\uc6a9 \uc77d\uae30",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub: \ud30c\uc77c \ub0b4\uc6a9 \uc77d\uae30": {
      "main": [
        [
          {
            "node": "\ud30c\uc77c \ub0b4\uc6a9 \ub514\ucf54\ub529",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud30c\uc77c \ub0b4\uc6a9 \ub514\ucf54\ub529": {
      "main": [
        [
          {
            "node": "Gemini: \ucf54\ub4dc \uc218\uc815",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini: \ucf54\ub4dc \uc218\uc815": {
      "main": [
        [
          {
            "node": "\uc218\uc815 \uacb0\uacfc \ud30c\uc2f1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\uc218\uc815 \uacb0\uacfc \ud30c\uc2f1": {
      "main": [
        [
          {
            "node": "IF: \uc218\uc815\ub41c \ud30c\uc77c \uc788\ub294\uc9c0 \ud655\uc778",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: \uc218\uc815\ub41c \ud30c\uc77c \uc788\ub294\uc9c0 \ud655\uc778": {
      "main": [
        [
          {
            "node": "GitHub: Trees API \ucee4\ubc0b",
            "type": "main",
            "index": 0
          },
          {
            "node": "GitHub: PR \ucf54\uba58\ud2b8 \uac8c\uc2dc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub: Trees API \ucee4\ubc0b": {
      "main": [
        [
          {
            "node": "Discord: \uc790\ub3d9 \uc218\uc815 \uc54c\ub9bc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub: PR \ucf54\uba58\ud2b8 \uac8c\uc2dc": {
      "main": [
        [
          {
            "node": "Discord: \uc790\ub3d9 \uc218\uc815 \uc54c\ub9bc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": [
    {
      "name": "jump-section"
    },
    {
      "name": "auto-fix"
    }
  ]
}
Pro

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

How this works

This workflow automates the correction of inline code comments in GitHub pull request reviews from Gemini, saving developers hours of manual fixes and ensuring code quality without disrupting the review process. It is ideal for engineering teams using Gemini for AI-assisted PR reviews who want to streamline their CI/CD pipeline. The key step involves extracting PR details, fetching review comments and changed files via GitHub's API using httpRequest, then preparing context to identify and separate comments needing updates.

Use this workflow when your team relies on Gemini for PR feedback and faces frequent issues with inaccurate inline suggestions that require quick revisions. Avoid it for non-Gemini reviews or repositories without GitHub integration, as it depends on specific event triggers. Common variations include adapting it for other AI tools like Claude by modifying the initial IF node, or extending it to auto-apply fixes via additional GitHub actions.

About this workflow

jump-section: Auto Fix Pipeline. Uses httpRequest. Webhook trigger; 16 nodes.

Source: https://github.com/bae080311/jump-section/blob/c566471882fa8b8f0e4d00704693b7c92d62c519/.n8n/workflows/auto-fix-pipeline.json — original creator credit. Request a take-down →

More General workflows → · Browse all categories →

Related workflows

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

General

Portfolio Orchestrator. Uses httpRequest. Webhook trigger; 59 nodes.

HTTP Request
General

jump-section: Comment Fix Pipeline. Uses httpRequest. Webhook trigger; 24 nodes.

HTTP Request
General

GitHub Issues Router (Linear / Jira / ClickUp). Uses stickyNote, httpRequest, respondToWebhook. Webhook trigger; 23 nodes.

HTTP Request
General

Form to CRM Lead Router (Pipedrive / HubSpot / Salesforce). Uses stickyNote, httpRequest, respondToWebhook. Webhook trigger; 22 nodes.

HTTP Request
General

Calendly to CRM Sync (Pipedrive / HubSpot / Salesforce). Uses stickyNote, httpRequest, respondToWebhook. Webhook trigger; 22 nodes.

HTTP Request