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
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 →