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 →
{
"name": "v3.7",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "gitlab-issue-webhook",
"options": {}
},
"id": "181e695e-f50a-4e4c-be6a-b0c6e8567760",
"name": "GitLab Issue Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-1840,
-368
]
},
{
"parameters": {
"jsCode": "// Process GitLab issue webhook for automatic implementation\nconst webhookData = $('GitLab Issue Webhook').first().json.body;\n\n// Only process issue creation events\nif (webhookData.object_kind !== 'issue' || webhookData.object_attributes?.action !== 'open') {\n return [];\n}\n\nconst issue = webhookData.object_attributes;\nconst author = webhookData.user;\nconst project = webhookData.project;\n\n// Extract issue details\nconst issueTitle = issue.title;\nconst issueDescription = issue.description || 'No description provided';\nconst issueId = issue.iid;\nconst issueUrl = issue.url;\n\n// Create branch name for this implementation\nconst branchName = `claude-auto-${issueId}-${Date.now()}`;\n\n// Build context for processing\nreturn [{\n json: {\n issueTitle: issueTitle,\n issueId: issueId,\n issueDescription: issueDescription,\n issueUrl: issueUrl,\n issueAuthor: author.name,\n projectId: project.id,\n projectName: project.name,\n projectPath: project.path,\n projectNamespace: project.path_with_namespace,\n branchName: branchName,\n timestamp: new Date().toISOString()\n }\n}];"
},
"id": "3085ff04-7c67-4f81-9527-4cd49d4b4400",
"name": "Process Issue",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1504,
-368
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Issue').item.json.projectName }} && git checkout main && git pull origin main && git reset --hard HEAD && git clean -fd"
},
"id": "08c48068-82d4-4c7a-a6d0-3007f24304d5",
"name": "Workspace Cleanup",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-1280,
-368
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Issue').item.json.projectName }} && git checkout -b {{ $('Process Issue').item.json.branchName }}"
},
"id": "ca1246ec-7f82-45b9-bfa3-03b63ab39e57",
"name": "Create Branch",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-1056,
-368
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Issue').item.json.projectName }} && echo '{{ $('Prepare Plan Prompt').item.json.planPromptEscaped }}' | claude --dangerously-skip-permissions"
},
"id": "751dede3-a05f-4c1f-b171-bb25e959a9f3",
"name": "Create Plan",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-1280,
-144
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Issue').item.json.projectName }} && git config user.name \"{{ $('Process Issue').item.json.issueAuthor }}\" && git config user.email \"claude-code@automated\" && echo '{{ $('Prepare Implementation Prompt').item.json.implementPromptEscaped }}' | claude --dangerously-skip-permissions"
},
"id": "25623a34-0a84-46ea-8fdf-12de07804bb5",
"name": "Implement Code",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-1280,
64
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Issue').item.json.projectName }} && git add . && git commit -m \"Implement: {{ $('Process Issue').item.json.issueTitle }}\""
},
"id": "13c96190-7a78-4519-9a09-350621963656",
"name": "Commit Implementation",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-1056,
64
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Issue').item.json.projectName }} && git push -u origin {{ $('Process Issue').item.json.branchName }}"
},
"id": "6b61c6d6-b0d0-479d-9862-7770c6bb3cee",
"name": "Push Branch",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-832,
64
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Issue').item.json.projectName }} && glab mr create --title \"Auto Implementation: {{ $('Process Issue').item.json.issueTitle }}\" --description \"Auto-implementation by Claude Code for issue #{{ $('Process Issue').item.json.issueId }}\\n\\nOriginal issue: {{ $('Process Issue').item.json.issueDescription }}\\n\\nImplemented via automatic workflow.\" --source-branch {{ $('Process Issue').item.json.branchName }} --target-branch main"
},
"id": "b1de4a07-d652-4045-a91e-47bc4f13558d",
"name": "Create MR",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-1504,
288
]
},
{
"parameters": {
"jsCode": "// Create planning prompt with full issue context\nconst context = $('Process Issue').item.json;\n\nconst planPrompt = `GitLab Issue Planning Request\n\nIssue Details:\nTitle: ${context.issueTitle}\nDescription: ${context.issueDescription}\nAuthor: ${context.issueAuthor}\nURL: ${context.issueUrl}\n\nPlease analyze this issue and create a detailed implementation plan. Follow these steps:\n\n1. ANALYZE: Understand what needs to be implemented based on the issue description\n2. PLAN: Create a step-by-step implementation plan that includes:\n - What files need to be created or modified\n - What functionality needs to be added\n - Any dependencies or prerequisites\n - Expected user interactions and flows\n - Testing considerations\n\n3. DESIGN: If this involves UI changes, describe:\n - Visual components and layout\n - User interface elements\n - Styling and visual hierarchy\n - User experience flow\n\nProvide a comprehensive plan that will guide the implementation phase. Be specific about technical details and implementation steps.`;\n\n// Escape the prompt for shell execution\nconst planPromptEscaped = planPrompt.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"'\\\"'\\\"'\");\n\nreturn [{\n json: {\n ...context,\n planPrompt: planPrompt,\n planPromptEscaped: planPromptEscaped\n }\n}];"
},
"id": "92746059-1d00-4949-9900-baa268746649",
"name": "Prepare Plan Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1504,
-144
]
},
{
"parameters": {
"jsCode": "// Extract plan from Claude output and prepare for posting\nconst planOutput = $('Create Plan').item.json;\nconst context = $('Process Issue').item.json;\n\nlet plan = '';\nif (planOutput.exitCode === 0 && planOutput.stdout) {\n plan = planOutput.stdout.trim();\n} else {\n plan = 'Plan generation failed. Proceeding with basic implementation approach.';\n}\n\n// Create plan comment for GitLab\nconst planComment = `\ud83d\udccb **Implementation Plan**\n\n${plan}\n\n---\n*\ud83e\udd16 Auto-generated plan for issue #${context.issueId}*\n*Implementation will begin shortly...*`;\n\nconst commentEscaped = planComment.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"'\\\"'\\\"'\");\n\nreturn [{\n json: {\n ...context,\n plan: plan,\n planComment: planComment,\n commentEscaped: commentEscaped\n }\n}];"
},
"id": "c678f231-52a9-4e61-82c0-296a62e0d691",
"name": "Prepare Plan Comment",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1056,
-144
]
},
{
"parameters": {
"operation": "createComment",
"owner": "={{ $('Process Issue').item.json.projectNamespace.split('/')[0] }}",
"repository": "={{ $('Process Issue').item.json.projectName }}",
"issueNumber": "={{ $('Process Issue').item.json.issueId }}",
"body": "={{ $('Prepare Plan Comment').item.json.planComment }}"
},
"id": "9e60d57b-ea4e-4fe3-a914-c1017b594201",
"name": "Post Plan Comment",
"type": "n8n-nodes-base.gitlab",
"typeVersion": 1,
"position": [
-832,
-144
],
"credentials": {
"gitlabApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Create implementation prompt with both issue context and generated plan\nconst context = $('Prepare Plan Comment').item.json;\n\nconst implementPrompt = `GitLab Issue Implementation Request\n\nIssue Details:\nTitle: ${context.issueTitle}\nDescription: ${context.issueDescription}\nAuthor: ${context.issueAuthor}\nURL: ${context.issueUrl}\n\nImplementation Plan:\n${context.plan}\n\nInstructions:\n\n1. FOLLOW THE PLAN: Implement the solution according to the detailed plan above\n2. QUALITY ASSURANCE: Ensure high code quality by:\n - Following best practices and coding standards\n - Adding appropriate error handling\n - Including necessary imports and dependencies\n - Ensuring proper code structure and organization\n - Running any necessary build/compile checks\n\n3. COMPLETENESS: Implement all aspects mentioned in the plan\n4. TESTING: Consider edge cases and ensure the implementation works as expected\n\nThe workflow will handle committing and pushing your changes automatically. Focus on delivering a complete, high-quality implementation that follows the plan.`;\n\n// Escape the prompt for shell execution\nconst implementPromptEscaped = implementPrompt.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"'\\\"'\\\"'\");\n\nreturn [{\n json: {\n ...context,\n implementPrompt: implementPrompt,\n implementPromptEscaped: implementPromptEscaped\n }\n}];"
},
"id": "13059283-b888-462c-bfac-443ea7cbe68a",
"name": "Prepare Implementation Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1504,
64
]
},
{
"parameters": {
"jsCode": "// Extract MR URL from glab output and prepare success comment\nconst mrOutput = $('Create MR').item.json;\nconst context = $('Process Issue').item.json;\n\nlet mrUrl = '';\nif (mrOutput.exitCode === 0 && mrOutput.stdout) {\n // Extract MR URL from glab output\n const urlMatch = mrOutput.stdout.match(/https:\\/\\/[^\\s]+/g);\n if (urlMatch && urlMatch.length > 0) {\n mrUrl = urlMatch[urlMatch.length - 1]; // Get the last URL which should be the MR URL\n }\n}\n\n// Create success comment\nconst successComment = `\u2705 **Auto-Implementation Complete!**\n\nI've automatically implemented a solution for this issue based on the generated plan.\n\n**Branch Created:** \\`${context.branchName}\\`\n${mrUrl ? `**Merge Request:** ${mrUrl}` : '**Note:** Merge request creation may have failed - please check manually.'}\n\n**What was implemented:**\nThe implementation follows the detailed plan posted earlier and is ready for review.\n\n---\n*\ud83e\udd16 Automated implementation via GitLab issue workflow*`;\n\nconst commentEscaped = successComment.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"'\\\"'\\\"'\");\n\nreturn [{\n json: {\n ...context,\n mrUrl: mrUrl,\n successComment: successComment,\n commentEscaped: commentEscaped,\n mrExitCode: mrOutput.exitCode,\n mrOutput: mrOutput.stdout || mrOutput.stderr\n }\n}];"
},
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Prepare Success Comment",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1280,
288
]
},
{
"parameters": {
"operation": "createComment",
"owner": "={{ $('Process Issue').item.json.projectNamespace.split('/')[0] }}",
"repository": "={{ $('Process Issue').item.json.projectName }}",
"issueNumber": "={{ $('Process Issue').item.json.issueId }}",
"body": "={{ $('Prepare Success Comment').item.json.successComment }}"
},
"id": "b2c3d4e5-f6g7-8901-bcde-f23456789012",
"name": "Post Success Comment",
"type": "n8n-nodes-base.gitlab",
"typeVersion": 1,
"position": [
-1056,
288
],
"credentials": {
"gitlabApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"content": "## Issue Processing",
"height": 192,
"width": 816,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-1696,
-432
],
"typeVersion": 1,
"id": "33b32a85-0102-4aea-a0b5-853574f5829a",
"name": "Sticky Note"
},
{
"parameters": {
"content": "## Planning",
"height": 240,
"width": 816,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-1696,
-240
],
"typeVersion": 1,
"id": "35c79c99-fceb-480d-bd6c-b1a417d52d77",
"name": "Sticky Note1"
},
{
"parameters": {
"content": "## Implementation",
"height": 208,
"width": 816,
"color": 6
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-1696,
0
],
"typeVersion": 1,
"id": "86013b29-24a2-42fc-a36b-d3649cdec866",
"name": "Sticky Note2"
},
{
"parameters": {
"content": "## Create MR\n## and Notify",
"height": 224,
"width": 816,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-1696,
208
],
"typeVersion": 1,
"id": "bbf21137-c7fe-45ef-8edd-500349db68c1",
"name": "Sticky Note3"
}
],
"connections": {
"GitLab Issue Webhook": {
"main": [
[
{
"node": "Process Issue",
"type": "main",
"index": 0
}
]
]
},
"Process Issue": {
"main": [
[
{
"node": "Workspace Cleanup",
"type": "main",
"index": 0
}
]
]
},
"Workspace Cleanup": {
"main": [
[
{
"node": "Create Branch",
"type": "main",
"index": 0
}
]
]
},
"Create Branch": {
"main": [
[
{
"node": "Prepare Plan Prompt",
"type": "main",
"index": 0
}
]
]
},
"Prepare Plan Prompt": {
"main": [
[
{
"node": "Create Plan",
"type": "main",
"index": 0
}
]
]
},
"Create Plan": {
"main": [
[
{
"node": "Prepare Plan Comment",
"type": "main",
"index": 0
}
]
]
},
"Prepare Plan Comment": {
"main": [
[
{
"node": "Post Plan Comment",
"type": "main",
"index": 0
}
]
]
},
"Post Plan Comment": {
"main": [
[
{
"node": "Prepare Implementation Prompt",
"type": "main",
"index": 0
}
]
]
},
"Prepare Implementation Prompt": {
"main": [
[
{
"node": "Implement Code",
"type": "main",
"index": 0
}
]
]
},
"Implement Code": {
"main": [
[
{
"node": "Commit Implementation",
"type": "main",
"index": 0
}
]
]
},
"Commit Implementation": {
"main": [
[
{
"node": "Push Branch",
"type": "main",
"index": 0
}
]
]
},
"Push Branch": {
"main": [
[
{
"node": "Create MR",
"type": "main",
"index": 0
}
]
]
},
"Create MR": {
"main": [
[
{
"node": "Prepare Success Comment",
"type": "main",
"index": 0
}
]
]
},
"Prepare Success Comment": {
"main": [
[
{
"node": "Post Success Comment",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "83760934-05b9-4731-be91-a69594261028",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "KRzL0kx7EIwEWlLm",
"tags": []
}
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.
gitlabApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
This workflow automates the entire process of turning a GitLab issue into implemented code, saving developers hours of manual setup and execution while ensuring consistent, error-free deployments. It is ideal for solo developers or small teams managing repositories on GitLab who want to streamline issue resolution without constant oversight. The key step involves a series of executeCommand nodes that handle workspace cleanup, branch creation, code implementation, committing, and pushing changes, all triggered seamlessly by a GitLab webhook.
Use this workflow when handling routine bug fixes or feature implementations in a GitLab project that require automated scripting via command-line tools, particularly for repetitive tasks like generating code from issue descriptions. Avoid it for complex, creative coding needs that demand human review at every stage, or if your setup lacks reliable command execution environments. Common variations include adding email notifications post-push or integrating with testing tools like Jenkins for validation before merging.
About this workflow
v3.7. Uses executeCommand, gitlab, stickyNote. Webhook trigger; 19 nodes.
Source: https://gitlab.com/huyhq8/gitlab-ai-automation/-/blob/develop/n8n/workflows/v3_7.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
v3.6. Uses executeCommand, gitlab, stickyNote. Webhook trigger; 17 nodes.
Claude Comment Handler v2. Uses executeCommand, gitlab. Webhook trigger; 15 nodes.
Claude Comment Handler v3 - Happy Path. Uses executeCommand, gitlab. Webhook trigger; 13 nodes.
Claude Comment Handler. Uses executeCommand, gitlab. Webhook trigger; 13 nodes.
Gitlab-Issue-Analyzer. Uses executeCommand, gitlab. Webhook trigger; 8 nodes.