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.6",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "claude-comment-webhook",
"options": {}
},
"id": "181e695e-f50a-4e4c-be6a-b0c6e8567760",
"name": "GitLab Comment Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-1840,
-368
]
},
{
"parameters": {
"jsCode": "// Process GitLab comment webhook and detect @claude-code mentions\nconst webhookData = $('GitLab Comment Webhook').first().json.body;\n\n// Only process issue comment events\nif (webhookData.object_kind !== 'note' || webhookData.object_attributes?.noteable_type !== 'Issue') {\n return [];\n}\n\nconst comment = webhookData.object_attributes.note;\nconst author = webhookData.user;\nconst issue = webhookData.issue;\n\n// Check if comment mentions @claude-code\nif (!comment.includes('@claude-code')) {\n return [];\n}\n\n// Extract command text (everything after @claude-code) - use same pattern as other workflows\nconst commandText = comment.replace(/@claude-code\\s*/, '').trim();\nconst commandTextShort = commandText.length > 50 ? commandText.substring(0, 47) + '...' : commandText;\n\n// Build context matching other workflows\nreturn [{\n json: {\n commandText: commandText,\n commandTextShort: commandTextShort,\n issueTitle: issue.title,\n issueId: issue.iid,\n issueDescription: issue.description,\n issueUrl: issue.url,\n issueAuthor: author.name,\n projectId: webhookData.project.id,\n projectName: webhookData.project.name,\n projectPath: webhookData.project.path,\n projectNamespace: webhookData.project.path_with_namespace,\n commentId: webhookData.object_attributes.id,\n commentAuthor: author.name,\n commentUrl: webhookData.object_attributes.url,\n branchName: `claude-${issue.iid}-${webhookData.object_attributes.id}`,\n timestamp: new Date().toISOString()\n }\n}];"
},
"id": "3085ff04-7c67-4f81-9527-4cd49d4b4400",
"name": "Process Comment",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1504,
-368
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Comment').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 Comment').item.json.projectName }} && git checkout -b {{ $('Process Comment').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 Comment').item.json.projectName }} && git config user.name \"{{ $('Process Comment').item.json.commentAuthor }}\" && git config user.email \"claude-code@automated\" && echo '{{ $('Prepare Prompt').item.json.implementPromptEscaped }}' | claude --dangerously-skip-permissions"
},
"id": "751dede3-a05f-4c1f-b171-bb25e959a9f3",
"name": "Implement Code",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-1280,
-144
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Comment').item.json.projectName }} && git add . && git commit -m \"Implement: {{ $('Process Comment').item.json.issueTitle }}\""
},
"id": "13c96190-7a78-4519-9a09-350621963656",
"name": "Commit Initial Implementation",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-1056,
-144
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Comment').item.json.projectName }} && echo '{{ $('Prepare Validation Prompt').item.json.validationPromptEscaped }}' | claude --dangerously-skip-permissions"
},
"id": "25623a34-0a84-46ea-8fdf-12de07804bb5",
"name": "Validate and Fix Build",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-1280,
64
]
},
{
"parameters": {
"command": "= cd /workspace/{{ $('Process Comment').item.json.projectName }} && git add . && if git diff --cached --quiet; then echo \"No changes to commit\"; else git commit -m \"Fix: Address build errors and syntax issues\"; fi && git push -u origin {{ $('Process Comment').item.json.branchName }}"
},
"id": "6b61c6d6-b0d0-479d-9862-7770c6bb3cee",
"name": "Commit Fixes and Push",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-1056,
64
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Comment').item.json.projectName }} && glab mr create --title \"Claude Implementation: {{ $('Process Comment').item.json.commandTextShort }}\" --description \"Auto-implementation by Claude Code for issue #{{ $('Process Comment').item.json.issueId }}\\n\\nOriginal request: {{ $('Process Comment').item.json.commandText }}\\n\\nImplemented via @claude-code automation.\" --source-branch {{ $('Process Comment').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 implementation prompt with full issue context\nconst context = $('Process Comment').item.json;\n\nconst implementPrompt = `GitLab Issue Implementation Request\n\nOriginal Issue:\nTitle: ${context.issueTitle}\nDescription: ${context.issueDescription}\nAuthor: ${context.issueAuthor}\nURL: ${context.issueUrl}\n\nComment from ${context.commentAuthor}:\n${context.commandText}\n\nCommand Type: @claude-code\n\nPlease follow these steps:\n\n1. DESIGN FIRST: Create a design preview/mockup of what you're about to build:\n - Save a design preview as 'design-preview.png' in the project root\n - If you cannot generate an image, create 'design-description.txt' with detailed UI description:\n * What UI components will be created\n * How they will be laid out\n * Key visual elements and styling\n * User interactions and flow\n * Color scheme and visual hierarchy\n\n2. IMPLEMENT: Then implement the requested changes following your design\n - Focus on implementing the functionality described in the issue and comment\n - Follow the design you created in step 1\n - Ensure the implementation matches your design preview\n\nThe workflow will handle committing and pushing your changes automatically.`;\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": "92746059-1d00-4949-9900-baa268746649",
"name": "Prepare Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1504,
-144
]
},
{
"parameters": {
"jsCode": "// Create Flutter-specific validation prompt with retry limit\nconst context = $('Process Comment').item.json;\n\nconst validationPrompt = `Flutter Error Detection and Fix\n\nCRITICAL: The initial implementation has been committed. Your job is to detect and fix ONLY actual Flutter errors.\n\nREQUIRED STEPS:\n1. Run: flutter analyze | grep \"error \u2022\" to find all errors\n2. IF errors are found:\n - Analyze each error message carefully\n - Fix ONLY the specific errors shown (missing imports, syntax errors, type errors, etc.)\n - Do NOT change working code or modify structure\n - Do NOT reformat or add features\n3. After fixing, run: flutter analyze | grep \"error \u2022\" again to verify errors are gone\n4. Repeat steps 1-3 until no errors remain OR maximum 3 attempts reached\n5. IF no errors found initially, do nothing - leave code as is\n\nRETRY LIMIT: Maximum 3 fix attempts. If errors persist after 3 attempts:\n- Stop trying to fix\n- Leave a comment in the code explaining what errors could not be fixed\n- Do not continue the loop\n\nIMPORTANT: \n- Use \"flutter analyze | grep \\\"error \u2022\\\"\" command to detect errors\n- Only fix actual Flutter analysis errors\n- Do not modify working code\n- Verify fixes by re-running the error detection command\n- Stop when no errors remain OR after 3 attempts\n- Prevent infinite loops by respecting the retry limit`;\n\n// Escape the prompt for shell execution\nconst validationPromptEscaped = validationPrompt.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"'\\\"'\\\"'\");\n\nreturn [{\n json: {\n ...context,\n validationPrompt: validationPrompt,\n validationPromptEscaped: validationPromptEscaped\n }\n}];"
},
"id": "13059283-b888-462c-bfac-443ea7cbe68a",
"name": "Prepare Validation 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 Comment').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 **Implementation Complete!**\n\nI've successfully implemented your request: \"${context.commandTextShort}\"\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 changes have been committed to the feature branch and are ready for review.\n\n---\n*\ud83e\udd16 Automated response via @claude-code*`;\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": "c678f231-52a9-4e61-82c0-296a62e0d691",
"name": "Prepare Success Comment",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1280,
288
]
},
{
"parameters": {
"operation": "createComment",
"owner": "={{ $('Process Comment').item.json.projectNamespace.split('/')[0] }}",
"repository": "={{ $('Process Comment').item.json.projectName }}",
"issueNumber": "={{ $('Process Comment').item.json.issueId }}",
"body": "={{ $('Prepare Success Comment').item.json.successComment }}"
},
"id": "9e60d57b-ea4e-4fe3-a914-c1017b594201",
"name": "Post Success Comment",
"type": "n8n-nodes-base.gitlab",
"typeVersion": 1,
"position": [
-1056,
288
],
"credentials": {
"gitlabApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"content": "## Preparation",
"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": "## Implementation",
"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": "## Review\n## and Fix",
"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 Comment Webhook": {
"main": [
[
{
"node": "Process Comment",
"type": "main",
"index": 0
}
]
]
},
"Process Comment": {
"main": [
[
{
"node": "Workspace Cleanup",
"type": "main",
"index": 0
}
]
]
},
"Workspace Cleanup": {
"main": [
[
{
"node": "Create Branch",
"type": "main",
"index": 0
}
]
]
},
"Create Branch": {
"main": [
[
{
"node": "Prepare Prompt",
"type": "main",
"index": 0
}
]
]
},
"Prepare Prompt": {
"main": [
[
{
"node": "Implement Code",
"type": "main",
"index": 0
}
]
]
},
"Implement Code": {
"main": [
[
{
"node": "Commit Initial Implementation",
"type": "main",
"index": 0
}
]
]
},
"Commit Initial Implementation": {
"main": [
[
{
"node": "Prepare Validation Prompt",
"type": "main",
"index": 0
}
]
]
},
"Prepare Validation Prompt": {
"main": [
[
{
"node": "Validate and Fix Build",
"type": "main",
"index": 0
}
]
]
},
"Validate and Fix Build": {
"main": [
[
{
"node": "Commit Fixes and Push",
"type": "main",
"index": 0
}
]
]
},
"Commit Fixes and Push": {
"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 code review and implementation in GitLab repositories, saving developers hours by turning comments into actionable changes without manual intervention. It's ideal for teams using GitLab for version control who want to streamline pull requests and ensure quick, accurate fixes. The process begins with a webhook capturing a comment on a merge request, then uses executeCommand nodes to clean the workspace, create a branch, implement the suggested code, commit changes, validate the build, and push fixes back to GitLab.
Use this workflow when handling repetitive code reviews in collaborative projects, such as open-source contributions or internal dev teams, to maintain momentum without context-switching. Avoid it for complex tasks requiring human oversight, like architectural decisions or security audits, where automation might introduce errors. Common variations include adding Slack notifications for status updates or integrating with testing tools like Jest for enhanced validation.
About this workflow
v3.6. Uses executeCommand, gitlab, stickyNote. Webhook trigger; 17 nodes.
Source: https://gitlab.com/huyhq8/gitlab-ai-automation/-/blob/develop/n8n/workflows/v3_6.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.7. Uses executeCommand, gitlab, stickyNote. Webhook trigger; 19 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.