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": "Claude Comment Handler v3 - Happy Path",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "claude-comment-webhook",
"options": {}
},
"id": "gitlab-webhook",
"name": "GitLab Comment Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
300
]
},
{
"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": "process-comment",
"name": "Process Comment",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
460,
300
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Comment').item.json.projectName }} && git checkout main && git pull origin main && git reset --hard HEAD && git clean -fd"
},
"id": "workspace-cleanup",
"name": "Workspace Cleanup",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
680,
300
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Comment').item.json.projectName }} && git checkout -b {{ $('Process Comment').item.json.branchName }}"
},
"id": "create-branch",
"name": "Create Branch",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
900,
300
]
},
{
"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": "implement-code",
"name": "Implement Code",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
1120,
300
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Comment').item.json.projectName }} && git add . && git commit -m \"Implement: {{ $('Process Comment').item.json.commandTextShort }}\""
},
"id": "commit-initial-implementation",
"name": "Commit Initial Implementation",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
1340,
160
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Comment').item.json.projectName }} && echo '{{ $('Prepare Validation Prompt').item.json.validationPromptEscaped }}' | claude --dangerously-skip-permissions"
},
"id": "validate-and-fix-build",
"name": "Validate and Fix Build",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
1340,
200
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Process Comment').item.json.projectName }} && git add . && git commit -m \"Fix: Address build errors and syntax issues\" && git push -u origin {{ $('Process Comment').item.json.branchName }}"
},
"id": "commit-fixes-and-push",
"name": "Commit Fixes and Push",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
1340,
260
]
},
{
"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": "create-mr",
"name": "Create MR",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
1340,
300
]
},
{
"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": "prepare-prompt",
"name": "Prepare Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1120,
200
]
},
{
"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": "prepare-validation-prompt",
"name": "Prepare Validation Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1340,
120
]
},
{
"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": "prepare-success-comment",
"name": "Prepare Success Comment",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1560,
300
]
},
{
"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": "post-success-comment",
"name": "Post Success Comment",
"type": "n8n-nodes-base.gitlab",
"typeVersion": 1,
"position": [
1780,
300
],
"credentials": {
"gitlabApi": {
"name": "<your credential>"
}
}
}
],
"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
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [],
"triggerCount": 0,
"updatedAt": "2025-01-29T00:00:00.000Z",
"versionId": "1"
}
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 handling of positive code review comments in GitLab, instantly turning feedback into implemented improvements to accelerate your development cycle and reduce manual effort. It's designed for developers and teams using GitLab who want seamless automation without switching tools. A GitLab webhook captures the comment, triggering a chain of executeCommand nodes that clean the workspace, create a branch, implement the suggested code, commit changes, validate the build, and push fixes—streamlining revisions so you can focus on innovation rather than routine tasks.
Use this for straightforward, 'happy path' comments on code reviews where the feedback is clear and actionable, such as minor fixes or enhancements in a GitLab repository. Avoid it for complex suggestions requiring human judgement or when dealing with negative feedback, as it assumes positive intent without escalation. Common variations include adapting the executeCommand scripts for different languages or integrating additional validation steps for larger projects.
About this workflow
Claude Comment Handler v3 - Happy Path. Uses executeCommand, gitlab. Webhook trigger; 13 nodes.
Source: https://gitlab.com/huyhq8/gitlab-ai-automation/-/blob/develop/n8n/workflows/claude-comment-handler-v3.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.
v3.6. Uses executeCommand, gitlab, stickyNote. Webhook trigger; 17 nodes.
Claude Comment Handler v2. Uses executeCommand, gitlab. Webhook trigger; 15 nodes.
Claude Comment Handler. Uses executeCommand, gitlab. Webhook trigger; 13 nodes.
Gitlab-Issue-Analyzer. Uses executeCommand, gitlab. Webhook trigger; 8 nodes.