AutomationFlowsAI & RAG › Generate Github Release Notes with AI Comparison

Generate Github Release Notes with AI Comparison

ByRichard Black @rtblack on n8n.io

Automatically generate GitHub release notes using AI.

Event trigger★★★★☆ complexityAI-powered16 nodesGithub TriggerGitHubOpenRouter ChatHTTP RequestAgent
AI & RAG Trigger: Event Nodes: 16 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #11569 — 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": "hAxEgO2Qq7o2nQkx",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "GitHub Release Comparison and Change Tracker",
  "tags": [
    {
      "id": "Ro0lWYYLMPqynw4Q",
      "name": "Content Generation",
      "createdAt": "2025-11-23T12:30:17.035Z",
      "updatedAt": "2025-11-23T12:30:17.035Z"
    },
    {
      "id": "dQwHNDJdfu7zMJIB",
      "name": "Release Notes Template",
      "createdAt": "2025-12-01T23:42:32.337Z",
      "updatedAt": "2025-12-01T23:42:32.337Z"
    },
    {
      "id": "k69nkZAyxqOV4Tf2",
      "name": "Docs",
      "createdAt": "2025-08-04T20:12:20.784Z",
      "updatedAt": "2025-08-04T20:12:20.784Z"
    }
  ],
  "nodes": [
    {
      "id": "0cd952c6-e9e5-4a18-8576-9ca8e5f77638",
      "name": "Github Trigger",
      "type": "n8n-nodes-base.githubTrigger",
      "position": [
        5392,
        2320
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "events": [
          "release"
        ],
        "options": {},
        "repository": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "authentication": "oAuth2"
      },
      "typeVersion": 1
    },
    {
      "id": "c2d5760d-0039-4dd0-bd77-13eec3fc178a",
      "name": "Get the last two release tags",
      "type": "n8n-nodes-base.code",
      "position": [
        6288,
        2320
      ],
      "parameters": {
        "jsCode": "const releases = $input.all().map(item => item.json); const published = releases.filter(r => !r.draft && !r.prerelease); published.sort((a, b) => { const aTime = a.published_at ? new Date(a.published_at).getTime() : 0; const bTime = b.published_at ? new Date(b.published_at).getTime() : 0; return bTime - aTime; }); let currentTag, previousTag; if (published.length >= 2) { currentTag = published[0].tag_name; previousTag = published[1].tag_name; } else if (published.length === 1) { currentTag = published[0].tag_name; const allReleases = releases.sort((a, b) => { const aTime = a.published_at || a.created_at ? new Date(a.published_at || a.created_at).getTime() : 0; const bTime = b.published_at || b.created_at ? new Date(b.published_at || b.created_at).getTime() : 0; return bTime - aTime; }); previousTag = allReleases.length >= 2 ? allReleases[1].tag_name : null; } else { throw new Error('No published releases found.'); } if (!previousTag) { throw new Error('Cannot find a previous release to compare against.'); } return [{ json: { currentTag, previousTag, owner: $(\"Set Repo & Owner\").first().json.owner, repo: $(\"Set Repo & Owner\").first().json.repo } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "65717c98-7491-4503-8f56-1a94a31af4f6",
      "name": "Fetch the latest release body",
      "type": "n8n-nodes-base.code",
      "position": [
        5616,
        2320
      ],
      "parameters": {
        "jsCode": "const body = $input.first().json.body;\n\nreturn [{\n  json: {\n    owner: body.repository.owner.login,\n    repo: body.repository.name,\n    currentTag: body.release.tag_name,\n    currentReleaseId: body.release.id,\n    currentReleaseUrl: body.release.html_url,\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "2439b062-6c7f-4390-a255-1962ec7a5b7a",
      "name": "Get the last two releases",
      "type": "n8n-nodes-base.github",
      "position": [
        6064,
        2320
      ],
      "parameters": {
        "limit": 2,
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $json.owner }}"
        },
        "resource": "release",
        "operation": "getAll",
        "repository": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $json.repo }}"
        },
        "authentication": "oAuth2"
      },
      "credentials": {
        "githubOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "b31292f7-5f66-4291-864b-411cc5b8f4f1",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        7032,
        2544
      ],
      "parameters": {
        "model": "anthropic/claude-3.7-sonnet",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "d7325423-b052-4418-bc58-39a4f66fcd85",
      "name": "OpenRouter Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        7384,
        2544
      ],
      "parameters": {
        "model": "google/gemini-2.5-flash-lite",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "5be3a018-42d4-4536-a061-2b9ce059c86b",
      "name": "Set Repo & Owner",
      "type": "n8n-nodes-base.set",
      "position": [
        5840,
        2320
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1c4df22b-d844-4a81-b720-1ef9c80507d6",
              "name": "=owner",
              "type": "string",
              "value": "={{ $json.owner }}"
            },
            {
              "id": "8216be2e-1bb2-4e23-8cc2-a53929cd7cfa",
              "name": "=repo",
              "type": "string",
              "value": "={{ $json.repo }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "a29301d9-ec7d-4604-bf36-39e3bd524612",
      "name": "Fetch release details from last two releases",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        6512,
        2320
      ],
      "parameters": {
        "url": "=https://api.github.com/repos/{{ $json.owner }}/{{ $json.repo }}/compare/{{ $json.previousTag }}...{{ $json.currentTag }}",
        "options": {},
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            }
          ]
        },
        "nodeCredentialType": "githubOAuth2Api"
      },
      "credentials": {
        "githubOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "768f2282-0f9d-4e66-9f04-68d58bd8da73",
      "name": "Confirm diff",
      "type": "n8n-nodes-base.code",
      "position": [
        6736,
        2320
      ],
      "parameters": {
        "jsCode": "// Take the compare response from the previous node\nconst compare = $input.first().json;\n\nconst commits = compare.commits || [];\nconst files = compare.files || [];\n\n// Determine if there are any changes at all\nconst hasChanges =\n  (compare.total_commits && compare.total_commits > 0) ||\n  (files.length > 0);\n\n// Simplify commits: only keep what the AI really needs\nconst simplifiedCommits = commits.map(c => ({\n  sha: c.sha,\n  message: c.commit?.message,\n  author: c.commit?.author?.name,\n  date: c.commit?.author?.date,\n}));\n\n// Simplify files: filename + status + quick stats + patch\nconst simplifiedFiles = files.map(f => ({\n  filename: f.filename,\n  status: f.status,  // added | modified | removed\n  additions: f.additions,\n  deletions: f.deletions,\n  changes: f.changes,\n  patch: f.patch,\n}));\n\nreturn [{\n  json: {\n    hasChanges,\n    status: compare.status,\n    ahead_by: compare.ahead_by,\n    behind_by: compare.behind_by,\n    total_commits: compare.total_commits,\n    commits: simplifiedCommits,\n    files: simplifiedFiles,\n    // Optional: if you already merged tags into this item upstream:\n    previousTag: $('Get the last two release tags').first().json.previousTag,\n    currentTag: $('Get the last two release tags').first().json.currentTag,\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "285daae3-d37d-4b36-985b-bc056700c4d9",
      "name": "Summarize release differences",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        6960,
        2320
      ],
      "parameters": {
        "text": "=You are a release notes assistant.\n\nYou will receive:\n- The previous version tag: {{ $json.previousTag }}\n- The current version tag: {{ $json.currentTag }}\n- A list of commits and changed files between these two versions.\n\nDiff JSON:\n```json\n{{ JSON.stringify($json) }}\n```\n\nTasks:\n\t1.\tGroup the changes into up to these sections:\n\t\u2022\tNew features\n\t\u2022\tImprovements\n\t\u2022\tBug fixes\n\t\u2022\tOther changes\n(Omit any section that would be empty.)\n\t2.\tUse short, user-facing bullet points (avoid internal noise like SHA hashes or low-level Git details).\n\t3.\tFor very small changes (like wording updates), explain the practical impact rather than just restating the change.\n\nOutput:\n\t\u2022\tReturn only Markdown.\n\t\u2022\tUse H2 (##) for section headings.\n\t\u2022\tUse bullet lists under each section.",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "7eb80e1d-e727-4724-bbb2-0a723f207f3c",
      "name": "Generate latest release notes",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        7312,
        2320
      ],
      "parameters": {
        "text": "=You are a release notes generator for a software product.\n\nYou receive a JSON object describing the differences between two versions of the code. It contains:\n\t\u2022\thasChanges: boolean indicating if there are any code changes.\n\t\u2022\tpreviousTag: previous version tag.\n\t\u2022\tcurrentTag: current version tag.\n\t\u2022\tcommits: list of commits with message, author, and date.\n\t\u2022\tfiles: list of changed files with filename, status (added | modified | removed), and basic stats (additions, deletions, changes). Some entries may also include a small text patch.\n\nHere is the diff JSON to use as input:\n{{ JSON.stringify($json) }}\n\nYour task is to generate user-facing release notes for version currentTag.\n\nRules:\n\t1.\tIf hasChanges is false or there are no commits/files, generate a short note explaining that there are no code changes between previousTag and currentTag. Keep it to 1\u20132 sentences.\n\t2.\tIf there are changes:\n\t\u2022\tGroup them into up to these sections:\n\t\u2022\tNew features\n\t\u2022\tImprovements\n\t\u2022\tBug fixes\n\t\u2022\tOther changes\n(Only include sections that actually have relevant changes.)\n\t\u2022\tUse short, clear bullet points under each section.\n\t\u2022\tFocus on user impact and behaviour, not internal implementation details. Avoid raw SHAs, internal ticket IDs, or low-level Git noise.\n\t\u2022\tIf the changes are very small (for example, text tweaks, description updates, or workflow naming changes), still explain why they matter in practical terms.\n\nFormatting requirements:\n\t\u2022\tOutput only Markdown.\n\t\u2022\tStart with a top-level heading or title line that includes the version, for example: \u201cRelease {{ $('Get the last two release tags').item.json.currentTag }}\".\"\n\t\u2022\tFor each section, use \u201c##\u201d headings (for example: \u201c## Improvements\u201d).\n\t\u2022\tUse Markdown bullet lists for the items.\n\t\u2022\tDo not include the raw JSON in the output.\n\t\u2022\tDo not include any explanation of your reasoning; only return the final release notes content.",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "f1bc152d-e70b-421d-912d-9e708ec8769c",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6008,
        2080
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 400,
        "content": "## Fetch last two releases\nThese nodes focus on fetching the details from the last two releases to make a comparison between your code change and to comfirm the difference between them."
      },
      "typeVersion": 1
    },
    {
      "id": "de566cf0-be47-4aee-a9c4-1f18d42a5009",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5568,
        2080
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 400,
        "content": "## Confirm latest release tag and details\nThese nodes fetch the key release details for improved outputs at the release note generation stages\n"
      },
      "typeVersion": 1
    },
    {
      "id": "3986fa32-6de6-4cff-a360-7783a2c9ba96",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6896,
        2080
      ],
      "parameters": {
        "color": 7,
        "width": 752,
        "height": 624,
        "content": "## Summarise and generate release notes\nThe first step of this stage is to analyze the code repository and confirm the change between the second last and last release.\n\nThen summarise the difference and use this to generate an accurate release note / changelog entry."
      },
      "typeVersion": 1
    },
    {
      "id": "27f04c30-2a84-43d1-9ca6-50c7013c025e",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5312,
        2080
      ],
      "parameters": {
        "color": 7,
        "height": 400,
        "content": "## GitHub Trigger\nThis workflow triggers when a new release is created in your GitHub repository with a new Git tag."
      },
      "typeVersion": 1
    },
    {
      "id": "b4938f59-3b03-4abb-b592-ae91d67dacde",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5328,
        1696
      ],
      "parameters": {
        "width": 1504,
        "height": 320,
        "content": "## How it works\n\n- A GitHub Release Trigger detects a newly published release.\n- The workflow fetches the latest and previous releases and prepares a comparison.\n- An AI Chat Model (via OpenRouter) summarises the diff and generates structured release notes.\n- The final output is ready to paste into GitHub Releases, documentation, or pipelines.\n\n## Setup steps\n\n- Connect your GitHub OAuth credential and select it in every GitHub node.\n- Add your OpenRouter API key as a credential and link it to each Chat Model node.\n- Import the template and confirm the repository settings.\n- (Optional) Adjust the AI prompts to customise tone or formatting."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "6b10afc6-bc8f-4218-81eb-38fb3fa111da",
  "connections": {
    "Confirm diff": {
      "main": [
        [
          {
            "node": "Summarize release differences",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Github Trigger": {
      "main": [
        [
          {
            "node": "Fetch the latest release body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Repo & Owner": {
      "main": [
        [
          {
            "node": "Get the last two releases",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Summarize release differences",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Generate latest release notes",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Get the last two releases": {
      "main": [
        [
          {
            "node": "Get the last two release tags",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch the latest release body": {
      "main": [
        [
          {
            "node": "Set Repo & Owner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get the last two release tags": {
      "main": [
        [
          {
            "node": "Fetch release details from last two releases",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Summarize release differences": {
      "main": [
        [
          {
            "node": "Generate latest release notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch release details from last two releases": {
      "main": [
        [
          {
            "node": "Confirm diff",
            "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 generate GitHub release notes using AI.

Source: https://n8n.io/workflows/11569/ — 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

Turn your code commits into engaging social media content automatically. This workflow monitors a GitHub repository, uses AI to write a LinkedIn post about your changes, generates a beautiful "Mac-win

Github Trigger, GitHub, LinkedIn +4
AI & RAG

Before adding a new npm package as a dependency, you should know if it's actively maintained, widely used, and safe to build on. This workflow does that analysis automatically.

@Mendable/N8N Nodes Firecrawl, OpenAI Chat, Form Trigger +6
AI & RAG

SecretOps is an n8n security automation workflow that monitors Git push events, detects high-risk secrets in commits, and automatically responds in real time.

OpenRouter Chat, Github Trigger, HTTP Request +4
AI & RAG

Github-Ai-Code-Review. Uses githubTrigger, httpRequest, agent, lmChatOpenAi. Event-driven trigger; 21 nodes.

Github Trigger, HTTP Request, Agent +2
AI & RAG

Code Review workflow. Uses lmChatOpenAi, githubTrigger, httpRequest, github. Event-driven trigger; 14 nodes.

OpenAI Chat, Github Trigger, HTTP Request +3