AutomationFlowsAI & RAG › Triage Github Issues to a Notion Board with Qwen via Openrouter

Triage Github Issues to a Notion Board with Qwen via Openrouter

ByĐỗ Thành Vinh (Kevin Do) @dothanhvinh on n8n.io

When a new issue is opened on a GitHub repository, this workflow uses an AI model to classify it by type (bug, feature, question, documentation, other), assign a priority level (high, medium, low), and generate a one-sentence summary. Results are written to a Notion database and…

Event trigger★★★★☆ complexityAI-powered17 nodesGithub TriggerNotionChain LlmOpenAI ChatGitHub
AI & RAG Trigger: Event Nodes: 17 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the Chainllm → OpenAI Chat 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": "FiSXmxC623RysdX7",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "GitHub Issues AI Triage \u2192 Notion Board",
  "tags": [],
  "nodes": [
    {
      "id": "d7cf2a5b-f06e-400e-8fb3-1976d5e14cc4",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -352,
        -560
      ],
      "parameters": {
        "width": 736,
        "height": 2200,
        "content": "## GitHub Issues AI Triage \u2192 Notion Board\n\nWhen a new issue is opened on a GitHub repository, this workflow uses an AI model to classify it by type (bug, feature, question, documentation, other), assign a priority level (high, medium, low), and generate a one-sentence summary. Results are written to a Notion database and posted as a comment on the GitHub issue.\n\nNo external triage service. No SaaS subscription. Runs on your own n8n instance with your own Notion database.\n\n### How it works\n\n1. **GitHub Webhook Trigger** \u2014 Listens for the \"issues\" event with action \"opened\" on the configured repository. Fires only when a new issue is created. Does not trigger on comments, edits, or closes.\n\n2. **Extract Issue Data** \u2014 Parses the webhook payload. GitHub Trigger wraps the full payload as a JSON string in the \"body\" field, so this node extracts the actual issue object, then pulls out the fields the workflow needs: issue number, title, body text (truncated to 2000 chars to save LLM tokens), URL, author, labels, and repository full name. Falls back to parsing the repository_url field if the repository object is missing from the payload.\n\n3. **Duplicate Check (Notion)** \u2014 Queries the Notion database for an existing entry with the same Issue URL. If a match is found, the workflow stops \u2014 this issue was already triaged. If the Notion API fails (timeout, rate limit, invalid token), the error branch fires and the workflow continues anyway. A failed duplicate check should not block triage.\n\n4. **AI Triage (Qwen 3 via OpenRouter)** \u2014 Sends the issue title and body to the model with a structured prompt that asks for: category, priority, summary, and suggested labels. The prompt includes clear definitions for each category and priority level so the model classifies consistently. If the LLM call fails, a fallback node assigns default values (category: other, priority: medium) so the issue is still tracked in Notion.\n\n5. **Create Notion Entry** \u2014 Writes a new row to the Notion database with all fields populated: title, issue URL, author, category, priority, summary, suggested labels, status (Open), source (ai or fallback), and creation date.\n\n6. **Post GitHub Comment** \u2014 Adds a formatted comment on the GitHub issue showing the triage result: category, priority, summary, and suggested labels. If the triage used fallback values (LLM was unavailable), a warning is included in the comment.\n\n### Why this approach\n\nMost issue triage tools push results back into GitHub as labels or comments. This workflow pushes to Notion instead. The reason: teams already use Notion as their project management hub. Having issues auto-classified and waiting in a Notion database means you can filter, sort, and assign without switching tools.\n\nThe AI model does classification, not decision-making. It doesn't close issues or assign people \u2014 it categorizes and summarizes. A human reviews the Notion board and decides what to act on. This keeps the human in the loop while removing the manual sorting work.\n\nThe duplicate check prevents wasted API calls. If an issue was already triaged (maybe someone triggered the webhook twice, or re-opened a closed issue), the workflow skips it instead of creating a duplicate Notion entry.\n\nSelf-hosted means your issue data never leaves your infrastructure. The only external call is to the LLM provider (OpenRouter by default), and you can replace that with a self-hosted model to keep everything internal.\n\n### Setup (10\u201315 min)\n\n- [ ] Create a GitHub Fine-grained Personal Access Token: Settings \u2192 Developer settings \u2192 Fine-grained tokens. Permissions: Issues (read/write), Webhooks (read/write). Scope it to the repository you want to watch.\n- [ ] In n8n, add the GitHub credential using the token.\n- [ ] Create a Notion database with these properties:\n  - Title (title)\n  - Issue URL (url)\n  - Author (rich_text)\n  - Category (select: bug / feature / question / documentation / other)\n  - Priority (select: high / medium / low)\n  - Summary (rich_text)\n  - Suggested Labels (rich_text)\n  - Status (select: Open / Closed)\n  - Source (select: ai / fallback)\n  - Created (date)\n- [ ] Create a Notion internal integration at https://www.notion.so/my-integrations. Copy the token.\n- [ ] In n8n, add the Notion credential using the integration token.\n- [ ] Share the Notion database with the integration (database \u2192 ... \u2192 Connections \u2192 select integration).\n- [ ] In the \"When Issue Opened\" node: set Owner and Repository to match your GitHub repo.\n- [ ] In the \"Verify Notion Duplicate\" and \"Add to Notion Board\" nodes: set the Database to your Notion database.\n- [ ] Add your OpenRouter API key to the \"OpenAI Qwen-3 Model\" node credential.\n- [ ] Activate the workflow. Open a test issue on your GitHub repo.\n\n### Customization\n\n- **Swap the LLM**: Change the model in \"OpenAI Qwen-3 Model\" to any OpenRouter-supported model (GPT-4o, Claude, Gemini). Or point to a self-hosted model by changing the base URL in the credential.\n- **Adjust triage rules**: Edit the prompt in \"Initiate AI Triage\" to add custom categories specific to your project (e.g., \"performance\", \"accessibility\", \"security\").\n- **Multi-repo**: Duplicate the workflow for each repo you want to triage, or modify the trigger to use a GitHub App that watches multiple repos.\n- **Add Slack or Discord notification**: After \"Post GitHub Comment\", add a node to send a summary to your team chat.\n- **Auto-assign**: Add logic after \"Format AI Triage Results\" to assign GitHub issues based on category (e.g., bugs to backend team, docs to content team).\n\n### What's next\n\nThis is a foundation. Here's where it can grow:\n\n- **Auto-label on GitHub** \u2014 Use the suggested labels from AI to automatically apply labels on the issue. Requires label creation in the repo first.\n- **Batch triage** \u2014 Add a \"Triage All Open Issues\" mode that processes existing untriaged issues, not just new ones.\n- **Priority-based routing** \u2014 High priority issues get a Slack or Discord notification immediately. Medium and low go to Notion only.\n- **Learning from feedback** \u2014 Track when a human changes the AI-assigned category in Notion, and use that as feedback to improve the prompt over time.\n- **Notion dashboard** \u2014 Build a Notion view with filters by category and priority. Add a \"Triage backlog\" view sorted by priority.\n\nSimple means it runs cheap, breaks rarely, and is easy to debug. Complexity can be added when the use case demands it.\n\n### Requirements\n\n- n8n instance (self-hosted or cloud)\n- GitHub Fine-grained Personal Access Token (Issues read/write, Webhooks read/write)\n- Notion account with internal integration\n- OpenRouter API key (or any OpenAI-compatible endpoint)"
      },
      "typeVersion": 1
    },
    {
      "id": "70e5462a-dd1b-460f-af28-23bdf6dcf5ea",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        416,
        -384
      ],
      "parameters": {
        "color": 7,
        "height": 496,
        "content": "## GitHub Issue Trigger\n\n**When Issue Opened** \u2014 GitHub Trigger node that registers a webhook on the configured repository. Listens only for the \"issues\" event. When someone opens a new issue, GitHub sends a POST request to the n8n webhook endpoint with the full issue payload.\n\nThe trigger does not fire on issue edits, comments, label changes, or closes. Only new issues enter this workflow.\n\nCredential needed: GitHub API (Fine-grained PAT with Issues read/write and Webhooks read/write permissions). Set the Owner and Repository fields to match the repo you want to watch."
      },
      "typeVersion": 1
    },
    {
      "id": "10cc12d3-1e74-41b9-9f72-328c71b5f1cb",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        688,
        -384
      ],
      "parameters": {
        "color": 7,
        "width": 624,
        "height": 704,
        "content": "## Extract and Check Issues\n\nFour nodes that parse the incoming issue and decide whether it needs triage.\n\n**Extract GitHub Issue Data** \u2014 The GitHub Trigger wraps the webhook payload as a JSON string inside the \"body\" field. This node parses that string, extracts the issue object, then pulls out the fields the workflow needs: issue number, title, body (truncated to 2000 characters to stay within LLM token limits), URL, author, labels, and repository full name. Falls back to parsing the repository_url field if the repository object is missing from the payload.\n\n**Verify Notion Duplicate** \u2014 Queries the Notion database using the Issue URL as a filter. If a matching page exists, the issue was already triaged and the workflow stops. \"Always Output Data\" is enabled so the node always produces output even when no match is found. \"onError\" is set to \"continueErrorOutput\" so a Notion API failure does not block triage \u2014 the error branch sends the issue forward anyway.\n\n**Identify New Issue** \u2014 Code node that checks the Notion query result. Three cases: (1) Notion returned an error object \u2192 issue passes through with duplicate_check \"error\"; (2) Notion returned an empty result (no match) \u2192 issue passes through with duplicate_check \"passed\"; (3) Notion found a matching page with an \"id\" field \u2192 returns empty array, workflow stops. This issue was already triaged \u2014 no duplicate entry created.\n\n**Bypass Duplicate Check** \u2014 Error branch from Verify Notion Duplicate. If the Notion API call fails (timeout, rate limit, invalid token), this node passes the issue forward with duplicate_check \"skipped\". The workflow continues to AI triage regardless of the Notion check failure. Better to over-triage than to miss an issue because of a transient API error."
      },
      "typeVersion": 1
    },
    {
      "id": "28ca7a2a-6c6a-476f-965f-6531d7d935ee",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1344,
        -448
      ],
      "parameters": {
        "color": 7,
        "width": 656,
        "height": 944,
        "content": "## AI Triage Processing\n\nFour nodes that handle the LLM call, its output, and error fallback.\n\n**Initiate AI Triage** \u2014 Chain LLM node that sends the issue title and body to the model with a structured prompt. The prompt defines five categories (bug, feature, question, documentation, other), three priority levels (high, medium, low), and asks for a one-sentence summary plus 1-3 suggested labels. Each level has clear definitions so the model classifies consistently. \"onError\" is set to \"continueErrorOutput\" \u2014 if the LLM call fails, the error branch catches it instead of crashing the workflow.\n\n**OpenAI Qwen-3 Model** \u2014 Sub-node providing the language model. Default is Qwen 3 235B via OpenRouter. Can be swapped for any OpenRouter-supported model (GPT-4o, Claude, Gemini) or a self-hosted model by changing the credential and model name.\n\n**Format AI Triage Results** \u2014 Parses the LLM output. Strips Qwen 3 thinking tags, extracts the JSON block from the response, validates each field against allowed values (category must be one of five options, priority must be one of three), and caps the summary at 200 characters. If parsing fails entirely, defaults to category \"other\", priority \"medium\". This node never throws \u2014 it always returns a valid triage object.\n\n**Handle AI Triage Error** \u2014 Error branch from Initiate AI Triage. If the LLM call fails (API timeout, rate limit, model unavailable), this node creates a fallback triage result: category \"other\", priority \"medium\", summary \"AI triage failed \u2014 manual review needed\", source \"fallback\". The issue still gets a Notion entry so it is never silently dropped."
      },
      "typeVersion": 1
    },
    {
      "id": "cce03a75-dbaa-4861-9e2e-5cc0dd334d05",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2032,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 608,
        "content": "## Notion Entry and GitHub Comment\n\nThree nodes that write the triage result to Notion and notify on GitHub.\n\n**Add to Notion Board** \u2014 Creates a new page in the Notion database with all triage fields: title, issue URL, author, category, priority, summary, suggested labels, status (defaults to \"Open\"), source (ai or fallback), and creation date. Both the success branch (Format AI Triage Results) and the error branch (Handle AI Triage Error) converge here \u2014 every issue that passes the duplicate check gets a Notion entry, whether triaged by AI or assigned fallback values.\n\n**Draft GitHub Comment** \u2014 Builds the comment body from the Notion entry. Reads triage fields from the Notion output (property_category, property_priority, property_summary, property_suggested_labels, property_source) and formats them into a readable comment with markdown. Extracts the repository owner and name from the issue URL by parsing the GitHub URL structure. If the source is \"fallback\", appends a warning that AI triage was unavailable.\n\n**Post GitHub Comment** \u2014 Posts the formatted comment on the GitHub issue using the Issues API. The comment shows category, priority, summary, and suggested labels so anyone visiting the issue can see the triage result at a glance. Owner and repository values are passed dynamically \u2014 this workflow does not hardcode any repository names."
      },
      "typeVersion": 1
    },
    {
      "id": "83463ad0-48b6-4108-9544-2583d4e29327",
      "name": "When Issue Opened",
      "type": "n8n-nodes-base.githubTrigger",
      "position": [
        448,
        144
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GITHUB_USERNAME",
          "cachedResultUrl": "",
          "cachedResultName": "YOUR_GITHUB_USERNAME"
        },
        "events": [
          "issues"
        ],
        "options": {},
        "repository": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_REPO_NAME",
          "cachedResultUrl": "",
          "cachedResultName": "YOUR_REPO_NAME"
        }
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e35f4b83-0567-4a7d-94f7-4b44d379ce3f",
      "name": "Extract GitHub Issue Data",
      "type": "n8n-nodes-base.code",
      "position": [
        720,
        144
      ],
      "parameters": {
        "jsCode": "const payload = $input.first().json;\n\n// GitHub Trigger wraps the webhook payload as a JSON string in \"body\"\nlet issue = {};\nlet repo = {};\n\ntry {\n  const webhookBody = typeof payload.body === 'string'\n    ? JSON.parse(payload.body)\n    : payload.body || {};\n\n  issue = webhookBody.issue || {};\n  repo = webhookBody.repository || {};\n} catch (e) {\n  // parse failed, keep empty objects\n}\n\n// Fallback: extract repo from repository_url if repo object is empty\n// URL format: https://api.github.com/repos/{owner}/{repo}\nlet repoFullName = repo.full_name || '';\nif (!repoFullName && issue.repository_url) {\n  const match = issue.repository_url.match(/repos\\/([^/]+\\/[^/]+)/);\n  if (match) repoFullName = match[1];\n}\n\nconst rawBody = issue.body;\nlet bodyText = '';\n\nif (rawBody && typeof rawBody === 'string') {\n  bodyText = rawBody.substring(0, 2000);\n} else {\n  bodyText = '';\n}\n\nconst issueData = {\n  issue_number: issue.number || 0,\n  title: String(issue.title || 'No title'),\n  body: bodyText,\n  url: String(issue.html_url || ''),\n  author: String(issue.user?.login || 'unknown'),\n  labels: (issue.labels || []).map(l => typeof l === 'string' ? l : String(l.name || '')),\n  created_at: String(issue.created_at || new Date().toISOString()),\n  repo_full_name: repoFullName\n};\n\nreturn [{ json: issueData }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "84786db5-892a-44b7-bd9b-0fafe243967a",
      "name": "Verify Notion Duplicate",
      "type": "n8n-nodes-base.notion",
      "onError": "continueErrorOutput",
      "position": [
        928,
        144
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "key": "Issue URL|url",
              "urlValue": "={{ $('Extract GitHub Issue Data').item.json.url }}",
              "condition": "equals"
            }
          ]
        },
        "options": {},
        "resource": "databasePage",
        "operation": "getAll",
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_NOTION_DATABASE_ID",
          "cachedResultUrl": "",
          "cachedResultName": "YOUR_NOTION_DATABASE_NAME"
        },
        "filterType": "manual"
      },
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2,
      "alwaysOutputData": true
    },
    {
      "id": "2735dcf4-bbde-44ac-85fe-dcae5fcdd04f",
      "name": "Identify New Issue",
      "type": "n8n-nodes-base.code",
      "position": [
        1152,
        128
      ],
      "parameters": {
        "jsCode": "const item = $input.first().json;\nconst issueData = $('Extract GitHub Issue Data').first().json;\n\n// Case 1: Notion query failed (API error, timeout, etc.)\n// item will have error-related fields, no \"id\"\n// \u2192 pass through, still triage the issue\nif (item.error || item.code || item.message) {\n  return [{ json: { ...issueData, duplicate_check: 'error' } }];\n}\n\n// Case 2: Notion returned empty object [{}] from \"Always Output Data\"\n// No match found \u2192 new issue \u2192 triage\nif (!item.id) {\n  return [{ json: { ...issueData, duplicate_check: 'passed' } }];\n}\n\n// Case 3: Notion found a matching page (has \"id\" field)\n// Already triaged \u2192 skip by returning empty\nreturn [];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a7c77b53-c388-457d-9fd4-53cddcdd511b",
      "name": "Bypass Duplicate Check",
      "type": "n8n-nodes-base.code",
      "position": [
        1152,
        368
      ],
      "parameters": {
        "jsCode": "const issueData = $('Extract GitHub Issue Data').first().json;\nreturn [{ json: { ...issueData, duplicate_check: 'skipped' } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "b4be1fc6-3638-4926-8170-93e6d7719233",
      "name": "Initiate AI Triage",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "onError": "continueErrorOutput",
      "position": [
        1456,
        128
      ],
      "parameters": {
        "text": "=You are a GitHub issue triage assistant. Analyze the issue below and respond with a valid JSON object only. No explanation, no markdown, just JSON.\n\n{\n  \"category\": \"bug\" | \"feature\" | \"question\" | \"documentation\" | \"other\",\n  \"priority\": \"high\" | \"medium\" | \"low\",\n  \"summary\": \"one-sentence plain English summary\",\n  \"suggested_labels\": [\"label1\", \"label2\"]\n}\n\nRules:\n- category \"bug\": something is broken, errors, unexpected behavior\n- category \"feature\": request for new functionality or improvement\n- category \"question\": asking how to do something, setup help\n- category \"documentation\": docs missing, unclear, or outdated\n- category \"other\": anything that doesn't fit above\n- priority \"high\": security issue, data loss, complete blocker, affects all users\n- priority \"medium\": significant but not critical, workaround exists\n- priority \"low\": nice to have, minor inconvenience, cosmetic\n- summary must be under 100 characters\n- suggested_labels: 1-3 short labels that describe the issue (lowercase, hyphenated)\n\nIssue title: {{ $json.title }}\nIssue body: {{ $json.body }}\nRepository: {{ $json.repo_full_name }}\n\nJSON:\n",
        "batching": {},
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "f967af95-0175-4292-9284-af462ff87eef",
      "name": "OpenAI Qwen-3 Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1456,
        336
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "qwen/qwen3-235b-a22b-2507",
          "cachedResultName": "qwen/qwen3-235b-a22b-2507"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "72a00802-ce61-4354-a22e-68570a45925c",
      "name": "Format AI Triage Results",
      "type": "n8n-nodes-base.code",
      "position": [
        1808,
        32
      ],
      "parameters": {
        "jsCode": "const llmOutput = $input.first().json;\nconst issueData = $('Extract GitHub Issue Data').first().json;\n\nconst result = {\n  category: 'other',\n  priority: 'medium',\n  summary: '',\n  suggested_labels: []\n};\n\ntry {\n  // n8n Chain LLM returns { output: \"...\" } or { text: \"...\" }\n  let raw = llmOutput.output || llmOutput.text || '';\n\n  // Strip thinking tags (Qwen 3 sometimes adds these)\n  raw = raw.replace(/&lt;think&gt;[\\s\\S]*?<\\/think>/gi, '').trim();\n\n  // Extract JSON block from response\n  const jsonMatch = raw.match(/\\{[\\s\\S]*?\\}/);\n  if (jsonMatch) {\n    const parsed = JSON.parse(jsonMatch[0]);\n\n    const validCategories = ['bug', 'feature', 'question', 'documentation', 'other'];\n    const validPriorities = ['high', 'medium', 'low'];\n\n    result.category = validCategories.includes(parsed.category) ? parsed.category : 'other';\n    result.priority = validPriorities.includes(parsed.priority) ? parsed.priority : 'medium';\n    result.summary = typeof parsed.summary === 'string' ? parsed.summary.substring(0, 200) : '';\n    result.suggested_labels = Array.isArray(parsed.suggested_labels)\n      ? parsed.suggested_labels.slice(0, 3).map(l => String(l).toLowerCase())\n      : [];\n  }\n} catch (e) {\n  // Parsing failed \u2014 keep defaults\n}\n\nreturn [{\n  json: {\n    issue_number: issueData.issue_number,\n    title: issueData.title,\n    url: issueData.url,\n    author: issueData.author,\n    created_at: issueData.created_at,\n    repo_full_name: issueData.repo_full_name,\n    category: result.category,\n    priority: result.priority,\n    summary: result.summary,\n    suggested_labels: result.suggested_labels,\n    source: 'ai'\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "654c4e78-a4de-4cf3-a871-39edf9219c73",
      "name": "Handle AI Triage Error",
      "type": "n8n-nodes-base.code",
      "position": [
        1808,
        224
      ],
      "parameters": {
        "jsCode": "const issueData = $('Extract GitHub Issue Data').first().json;\n\nreturn [{\n  json: {\n    issue_number: issueData.issue_number,\n    title: issueData.title,\n    url: issueData.url,\n    author: issueData.author,\n    created_at: issueData.created_at,\n    repo_full_name: issueData.repo_full_name,\n    category: 'other',\n    priority: 'medium',\n    summary: 'AI triage failed \u2014 manual review needed',\n    suggested_labels: [],\n    source: 'fallback'\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "84b26d3e-77fe-436d-981b-8a911a24fbe3",
      "name": "Add to Notion Board",
      "type": "n8n-nodes-base.notion",
      "position": [
        2064,
        128
      ],
      "parameters": {
        "title": "={{ $json.title }}",
        "blockUi": {
          "blockValues": []
        },
        "options": {},
        "resource": "databasePage",
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_NOTION_DATABASE_ID",
          "cachedResultUrl": "",
          "cachedResultName": "YOUR_NOTION_DATABASE_NAME"
        },
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "Issue URL|url",
              "urlValue": "={{ $('Extract GitHub Issue Data').item.json.url }}",
              "ignoreIfEmpty": true
            },
            {
              "key": "Author|rich_text",
              "text": {
                "text": [
                  {
                    "text": "={{ $json.author }}",
                    "annotationUi": {}
                  }
                ]
              },
              "richText": true
            },
            {
              "key": "Category|select",
              "selectValue": "={{ $json.category }}"
            },
            {
              "key": "Priority|select",
              "selectValue": "={{ $json.priority }}"
            },
            {
              "key": "Summary|rich_text",
              "text": {
                "text": [
                  {
                    "text": "={{ $json.summary }}",
                    "annotationUi": {}
                  }
                ]
              },
              "richText": true
            },
            {
              "key": "Suggested Labels|rich_text",
              "text": {
                "text": [
                  {
                    "text": "={{ $json.suggested_labels.join(', ') }}",
                    "annotationUi": {}
                  }
                ]
              },
              "richText": true
            },
            {
              "key": "Status|select",
              "selectValue": "Open"
            },
            {
              "key": "Source|select",
              "selectValue": "={{ $json.source }}"
            },
            {
              "key": "Created|date",
              "date": "={{ $json.created_at }}",
              "includeTime": false
            }
          ]
        }
      },
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "379fef55-b2d4-451d-90bb-2f0ce79f0259",
      "name": "Draft GitHub Comment",
      "type": "n8n-nodes-base.code",
      "position": [
        2320,
        128
      ],
      "parameters": {
        "jsCode": "const notion = $input.first().json;\nconst issue = $('Extract GitHub Issue Data').first().json;\n\nconst category = notion.property_category || 'unknown';\nconst priority = notion.property_priority || 'unknown';\nconst summary = notion.property_summary || '';\nconst suggestedLabels = notion.property_suggested_labels || '';\nconst source = notion.property_source || 'ai';\n\nlet body = '\ud83e\udd16 **Auto-Triage Result**\\n';\nbody += '- **Category:** ' + category + '\\n';\nbody += '- **Priority:** ' + priority + '\\n';\nbody += '- **Summary:** ' + summary + '\\n';\n\nif (suggestedLabels) {\n  body += '- **Suggested labels:** ' + suggestedLabels + '\\n';\n}\n\nif (source === 'fallback') {\n  body += '\\n\u26a0\ufe0f _AI triage was unavailable. This was assigned default values._\\n';\n}\n\nbody += '\\n_Triaged by n8n automation_';\n\nreturn [{\n  json: {\n    owner: issue.repo_full_name.split('/')[0],\n    repository: issue.repo_full_name.split('/')[1],\n    issue_number: issue.issue_number,\n    comment_body: body\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e702c7e9-c14f-4d0f-b62d-c091dab00327",
      "name": "Post GitHub Comment",
      "type": "n8n-nodes-base.github",
      "position": [
        2560,
        128
      ],
      "parameters": {
        "body": "={{ $json.comment_body }}",
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $json.owner }}"
        },
        "operation": "createComment",
        "repository": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $json.repository }}"
        },
        "issueNumber": "={{ $json.issue_number }}"
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "a54b4766-b99b-4bc0-96d1-2b0d382c1a49",
  "connections": {
    "When Issue Opened": {
      "main": [
        [
          {
            "node": "Extract GitHub Issue Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Identify New Issue": {
      "main": [
        [
          {
            "node": "Initiate AI Triage",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Initiate AI Triage": {
      "main": [
        [
          {
            "node": "Format AI Triage Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Handle AI Triage Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add to Notion Board": {
      "main": [
        [
          {
            "node": "Draft GitHub Comment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Qwen-3 Model": {
      "ai_languageModel": [
        [
          {
            "node": "Initiate AI Triage",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Draft GitHub Comment": {
      "main": [
        [
          {
            "node": "Post GitHub Comment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Bypass Duplicate Check": {
      "main": [
        [
          {
            "node": "Initiate AI Triage",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Handle AI Triage Error": {
      "main": [
        [
          {
            "node": "Add to Notion Board",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Notion Duplicate": {
      "main": [
        [
          {
            "node": "Identify New Issue",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Bypass Duplicate Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format AI Triage Results": {
      "main": [
        [
          {
            "node": "Add to Notion Board",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract GitHub Issue Data": {
      "main": [
        [
          {
            "node": "Verify Notion Duplicate",
            "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

When a new issue is opened on a GitHub repository, this workflow uses an AI model to classify it by type (bug, feature, question, documentation, other), assign a priority level (high, medium, low), and generate a one-sentence summary. Results are written to a Notion database and…

Source: https://n8n.io/workflows/15755/ — 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 template attempts to replicate OpenAI's DeepResearch feature which, at time of writing, is only available to their pro subscribers.

Output Parser Structured, OpenAI Chat, Form Trigger +8
AI & RAG

This workflow is perfect for creators, solopreneurs, and personal brands who want to consistently publish bold, high-performing content on X (Twitter) — without writing a single line themselves. After

OpenAI Chat, Memory Buffer Window, Tool Workflow +10
AI & RAG

AI Blog Publisher – Automated Blog Content Workflow This workflow is designed for individuals and teams who regularly publish content on their blog and want to automate the entire process from start t

WordPress, HTTP Request, Memory Buffer Window +9
AI & RAG

📌 How it works

Telegram Trigger, Telegram, OpenAI +5
AI & RAG

Use cases Automatically detect and classify sponsorship inquiries from your Gmail inbox Extract company name, contact, budget, campaign type, and channel from emails using AI Log every deal to a Notio

Gmail Trigger, OpenAI Chat, Chain Llm +3