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