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 v2",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "claude-comment-webhook",
"options": {}
},
"id": "webhook",
"name": "GitLab Comment Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
300
]
},
{
"parameters": {
"jsCode": "// Process GitLab comment webhook and detect @claude 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// Detect @claude mentions\nconst hasClaudeCode = comment.includes('@claude-code');\nconst hasClaudePlan = comment.includes('@claude-plan');\n\nif (!hasClaudeCode && !hasClaudePlan) {\n return []; // No Claude mention, skip processing\n}\n\n// Extract the command and context\nconst commandType = hasClaudeCode ? 'code' : 'plan';\nconst commandText = comment.replace(/@claude-(code|plan)\\s*/, '').trim();\n\n// Build base context for all nodes\nconst baseContext = {\n commandType: commandType,\n commandText: commandText,\n commandTextShort: commandText.length > 200 ? commandText.substring(0, 200) + '...' : commandText,\n issueTitle: issue.title,\n issueId: issue.iid,\n issueDescription: issue.description,\n issueUrl: issue.url,\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};\n\nreturn [{ json: baseContext }];"
},
"id": "type-detection",
"name": "Type Detection",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
460,
300
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $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": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"id": "condition1",
"leftValue": "={{ $json.commandType }}",
"rightValue": "plan",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "if-plan-or-code",
"name": "If: Plan or Code?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
900,
300
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Type Detection').item.json.projectName }} && echo \"GitLab Issue Guidance Request\n\nOriginal Issue:\nTitle: {{ $('Type Detection').item.json.issueTitle }}\nDescription: {{ $('Type Detection').item.json.issueDescription }}\nAuthor: {{ $('Type Detection').item.json.commentAuthor }}\nURL: {{ $('Type Detection').item.json.issueUrl }}\n\nComment from {{ $('Type Detection').item.json.commentAuthor }}:\n{{ $('Type Detection').item.json.commandText }}\n\nCommand Type: @claude-plan\n\nGUIDANCE MODE: Please provide helpful guidance and directly answer the specific question or request in the comment. Focus on being practical and concise rather than creating extensive documentation. Provide actionable advice that helps the user understand how to approach their specific situation.\" | claude --dangerously-skip-permissions"
},
"id": "generate-plan",
"name": "Generate Plan & Follow-up",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
1120,
200
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Type Detection').item.json.projectName }} && echo \"GitLab Issue Implementation Analysis Request\n\nOriginal Issue:\nTitle: {{ $('Type Detection').item.json.issueTitle }}\nDescription: {{ $('Type Detection').item.json.issueDescription }}\nAuthor: {{ $('Type Detection').item.json.commentAuthor }}\nURL: {{ $('Type Detection').item.json.issueUrl }}\n\nComment from {{ $('Type Detection').item.json.commentAuthor }}:\n{{ $('Type Detection').item.json.commandText }}\n\nCommand Type: @claude-code\n\nIMPLEMENTATION ANALYSIS MODE: Please analyze this code change request and attempt to create an implementation plan. Only determine that context is insufficient if you genuinely cannot understand what needs to be implemented.\n\nFirst, try to understand the request and create a plan based on:\n1. The issue description and context\n2. The comment request\n3. Your understanding of the codebase from the repository\n4. Reasonable assumptions about common implementation patterns\n\nIMPORTANT: You MUST respond with ONLY valid JSON in exactly this format:\n\n{\n \"hasEnoughContext\": true or false,\n \"reasoning\": \"Brief explanation of your decision\",\n \"analysis\": \"Detailed implementation plan if true, or specific questions/requirements if false\",\n \"confidence\": \"high\", \"medium\", or \"low\",\n \"nextAction\": \"implement\" or \"clarify\"\n}\n\nDo not include any text before or after the JSON. The response must be valid JSON that can be parsed programmatically.\" | claude --dangerously-skip-permissions"
},
"id": "analyze-change-req",
"name": "Analyze & Plan Change Request",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
1120,
400
]
},
{
"parameters": {
"jsCode": "// Parse Claude's JSON implementation analysis response\nconst claudeOutput = $('Analyze & Plan Change Request').item.json;\nconst baseContext = $('Type Detection').item.json;\n\n// Check for execution errors\nif (claudeOutput.exitCode !== 0) {\n return [{ json: { \n ...baseContext,\n hasEnoughContext: false,\n success: false, \n error: true,\n errorMessage: claudeOutput.stderr || 'Unknown error',\n analysis: claudeOutput.stdout || 'No analysis available'\n } }];\n}\n\nlet rawResponse = claudeOutput.stdout;\nif (!rawResponse || rawResponse.trim() === '') {\n return [{ json: { \n ...baseContext,\n hasEnoughContext: false,\n success: false, \n error: true,\n errorMessage: 'Empty response from Claude',\n analysis: 'No analysis generated'\n } }];\n}\n\n// Parse JSON response with validation\nlet analysisData;\ntry {\n // Clean the response - extract JSON from markdown code blocks if present\n let cleanResponse = rawResponse.trim();\n \n // Check if response is wrapped in markdown code blocks\n if (cleanResponse.startsWith('```json') || cleanResponse.startsWith('```')) {\n // Extract content between code block markers\n const lines = cleanResponse.split('\\n');\n const startIndex = lines[0].startsWith('```') ? 1 : 0;\n const endIndex = lines.lastIndexOf('```');\n if (endIndex > startIndex) {\n cleanResponse = lines.slice(startIndex, endIndex).join('\\n').trim();\n }\n }\n \n // Parse the clean JSON\n analysisData = JSON.parse(cleanResponse);\n} catch (parseError) {\n return [{ json: { \n ...baseContext,\n hasEnoughContext: false,\n success: false, \n error: true,\n errorMessage: `JSON parsing failed: ${parseError.message}`,\n analysis: `Invalid JSON response: ${rawResponse}`,\n rawResponse: rawResponse\n } }];\n}\n\n// Validate required JSON fields\nif (typeof analysisData.hasEnoughContext !== 'boolean' || !analysisData.analysis) {\n return [{ json: { \n ...baseContext,\n hasEnoughContext: false,\n success: false, \n error: true,\n errorMessage: 'Missing required fields in JSON response or hasEnoughContext is not boolean',\n analysis: analysisData.analysis || 'No analysis provided',\n rawResponse: rawResponse\n } }];\n}\n\n// Extract parsed data - direct boolean value, no conversion needed!\nconst hasEnoughContext = analysisData.hasEnoughContext;\nconst extractedAnalysis = analysisData.analysis;\nconst reasoning = analysisData.reasoning || 'No reasoning provided';\nconst confidence = analysisData.confidence || 'unknown';\nconst nextAction = analysisData.nextAction || (hasEnoughContext ? 'implement' : 'clarify');\nconst analysisType = hasEnoughContext ? 'implementation_plan' : 'clarification_needed';\n\n// Create follow-up questions prompt for insufficient context cases\nconst followUpPrompt = `GitLab Issue Follow-up Questions Request\n\nOriginal Issue:\nTitle: ${baseContext.issueTitle}\nDescription: ${baseContext.issueDescription}\nAuthor: ${baseContext.commentAuthor}\nURL: ${baseContext.issueUrl}\n\nComment from ${baseContext.commentAuthor}:\n${baseContext.commandText}\n\nInitial Analysis:\n${extractedAnalysis}\n\nCommand Type: @claude-code\n\nFOLLOW-UP QUESTIONS MODE: The initial analysis determined that there is insufficient context to implement the requested changes. Please provide:\n\n1. **My Concerns:** Clearly explain why I cannot proceed with implementation (what specific information is missing or unclear)\n\n2. **What You Need to Prepare:** Provide a clear list of information, decisions, or preparations the developer needs to make before I can implement this request\n\n3. **Specific Questions:** Ask targeted questions about:\n - Technical specifications needed\n - Implementation details that are unclear \n - Design decisions that need to be made\n - Dependencies or constraints to consider\n - Expected behavior and edge cases\n\nStructure your response to help the developer understand exactly what they need to provide for successful implementation.`;\n\n// Create implementation prompt for sufficient context cases\nconst implementPrompt = `GitLab Issue Implementation Request\n\nOriginal Issue:\nTitle: ${baseContext.issueTitle}\nDescription: ${baseContext.issueDescription}\nAuthor: ${baseContext.commentAuthor}\nURL: ${baseContext.issueUrl}\n\nComment from ${baseContext.commentAuthor}:\n${baseContext.commandText}\n\nContext Analysis:\n${extractedAnalysis}\n\nCommand Type: @claude-code\n\nIMPLEMENTATION MODE: Please implement the requested changes. You are already on the correct feature branch.\n\nSTEPS:\n1. Implement the requested changes\n2. Test your changes if possible\n3. Add and commit your changes with: git add . && git commit -m \"Implement: [brief description of changes]\"\n\nIMPORTANT: \n- Do NOT create a new branch (already done)\n- Do NOT push the branch (workflow handles this)\n- Focus on implementing the requested functionality\n- Commit your changes when complete`;\n\nreturn [{ json: {\n ...baseContext,\n hasEnoughContext: hasEnoughContext,\n analysisType: analysisType,\n analysis: extractedAnalysis,\n reasoning: reasoning,\n confidence: confidence,\n nextAction: nextAction,\n fullAnalysis: rawResponse,\n followUpPrompt: followUpPrompt,\n followUpPromptEscaped: followUpPrompt.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"'\\\"'\\\"'\"),\n implementPrompt: implementPrompt,\n implementPromptEscaped: implementPrompt.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"'\\\"'\\\"'\"),\n success: true,\n error: false\n} }];"
},
"id": "parse-context-analysis",
"name": "Parse Context Analysis",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1340,
400
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"id": "condition1",
"leftValue": "={{ $('Parse Context Analysis').item.json.hasEnoughContext }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "if-enough-context",
"name": "If: Enough Context?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1560,
400
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Type Detection').item.json.projectName }} && git checkout main && git pull origin main && git checkout -b claude-{{ $('Type Detection').item.json.issueId }}-{{ $('Type Detection').item.json.commentId }}"
},
"id": "create-impl-branch",
"name": "Create Implementation Branch",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
1780,
250
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Type Detection').item.json.projectName }} && git config user.name \"{{ $('Type Detection').item.json.commentAuthor }}\" && git config user.email \"claude-code@automated\" && echo '{{ $('Parse Context Analysis').item.json.implementPromptEscaped }} After implementing, please commit your changes with an appropriate commit message that describes what you implemented.' | claude --dangerously-skip-permissions"
},
"id": "implement-changes",
"name": "Implement Changes",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
1780,
300
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Type Detection').item.json.projectName }} && if git show-ref --verify --quiet refs/heads/claude-{{ $('Type Detection').item.json.issueId }}-{{ $('Type Detection').item.json.commentId }}; then git push -u origin claude-{{ $('Type Detection').item.json.issueId }}-{{ $('Type Detection').item.json.commentId }} && glab mr create --title \"Claude Implementation: {{ $('Type Detection').item.json.commandTextShort }}\" --description \"Auto-implementation by Claude Code for issue #{{ $('Type Detection').item.json.issueId }}\\n\\nOriginal request: {{ $('Type Detection').item.json.commandText }}\\n\\nImplemented via @claude-code automation.\" --source-branch claude-{{ $('Type Detection').item.json.issueId }}-{{ $('Type Detection').item.json.commentId }} --target-branch main; else echo \"ERROR: Branch claude-{{ $('Type Detection').item.json.issueId }}-{{ $('Type Detection').item.json.commentId }} was not created. Implementation may have failed.\"; exit 1; fi"
},
"id": "create-merge-request",
"name": "Create Merge Request",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
2000,
300
]
},
{
"parameters": {
"command": "=cd /workspace/{{ $('Type Detection').item.json.projectName }} && echo '{{ $('Parse Context Analysis').item.json.followUpPromptEscaped }}' | claude --dangerously-skip-permissions"
},
"id": "generate-followup-questions",
"name": "Generate Follow-up Questions",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
1780,
500
]
},
{
"parameters": {
"operation": "createComment",
"owner": "={{ $('Type Detection').item.json.projectNamespace.split('/')[0] }}",
"repository": "={{ $('Type Detection').item.json.projectName }}",
"issueNumber": "={{ $('Type Detection').item.json.issueId }}",
"body": "=\ud83e\udd16 **Claude Guidance** _(Generated for @claude-plan)_\n\n{{ $('Generate Plan & Follow-up').item.json.stdout || 'No guidance generated' }}\n\n---\n*Reply with @claude-plan for more guidance or @claude-code to implement specific parts*"
},
"id": "post-plan-comment",
"name": "Post Plan Comment",
"type": "n8n-nodes-base.gitlab",
"typeVersion": 1,
"position": [
1340,
200
],
"credentials": {
"gitlabApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "createComment",
"owner": "={{ $('Type Detection').item.json.projectNamespace.split('/')[0] }}",
"repository": "={{ $('Type Detection').item.json.projectName }}",
"issueNumber": "={{ $('Type Detection').item.json.issueId }}",
"body": "=\ud83e\udd16 **Implementation Complete**\n\nI've implemented your @claude-code request and created a merge request.\n\n**MR Link:** {{ $('Create Merge Request').item.json.stdout.match(/(https:\\/\\/[^\\s]+)/)?.[1] || 'Merge request created successfully' }}\n**Branch:** `claude-{{ $('Type Detection').item.json.issueId }}-{{ $('Type Detection').item.json.commentId }}`\n\n## What was implemented:\n\n{{ $('Type Detection').item.json.commandText }}\n\nPlease review the changes and merge if they meet your requirements.\n\n---\n*Reply with @claude-plan for implementation guidance or @claude-code for more changes*"
},
"id": "post-implementation-comment",
"name": "Post Implementation Comment",
"type": "n8n-nodes-base.gitlab",
"typeVersion": 1,
"position": [
2220,
300
],
"credentials": {
"gitlabApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "createComment",
"owner": "={{ $('Type Detection').item.json.projectNamespace.split('/')[0] }}",
"repository": "={{ $('Type Detection').item.json.projectName }}",
"issueNumber": "={{ $('Type Detection').item.json.issueId }}",
"body": "=\ud83e\udd16 **Need More Information** _(Generated for @claude-code)_\n\n{{ $('Generate Follow-up Questions').item.json.stdout || 'Please provide more specific details about your request.' }}\n\n---\n*Please provide the additional details requested above, then use @claude-code again to proceed with implementation*"
},
"id": "post-followup-comment",
"name": "Post Follow-up Comment",
"type": "n8n-nodes-base.gitlab",
"typeVersion": 1,
"position": [
2000,
500
],
"credentials": {
"gitlabApi": {
"name": "<your credential>"
}
}
}
],
"connections": {
"GitLab Comment Webhook": {
"main": [
[
{
"node": "Type Detection",
"type": "main",
"index": 0
}
]
]
},
"Type Detection": {
"main": [
[
{
"node": "Workspace Cleanup",
"type": "main",
"index": 0
}
]
]
},
"Workspace Cleanup": {
"main": [
[
{
"node": "If: Plan or Code?",
"type": "main",
"index": 0
}
]
]
},
"If: Plan or Code?": {
"main": [
[
{
"node": "Generate Plan & Follow-up",
"type": "main",
"index": 0
}
],
[
{
"node": "Analyze & Plan Change Request",
"type": "main",
"index": 0
}
]
]
},
"Generate Plan & Follow-up": {
"main": [
[
{
"node": "Post Plan Comment",
"type": "main",
"index": 0
}
]
]
},
"Analyze & Plan Change Request": {
"main": [
[
{
"node": "Parse Context Analysis",
"type": "main",
"index": 0
}
]
]
},
"Parse Context Analysis": {
"main": [
[
{
"node": "If: Enough Context?",
"type": "main",
"index": 0
}
]
]
},
"If: Enough Context?": {
"main": [
[
{
"node": "Create Implementation Branch",
"type": "main",
"index": 0
}
],
[
{
"node": "Generate Follow-up Questions",
"type": "main",
"index": 0
}
]
]
},
"Create Implementation Branch": {
"main": [
[
{
"node": "Implement Changes",
"type": "main",
"index": 0
}
]
]
},
"Implement Changes": {
"main": [
[
{
"node": "Create Merge Request",
"type": "main",
"index": 0
}
]
]
},
"Create Merge Request": {
"main": [
[
{
"node": "Post Implementation Comment",
"type": "main",
"index": 0
}
]
]
},
"Generate Follow-up Questions": {
"main": [
[
{
"node": "Post Follow-up Comment",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"meta": {
"templateCredsSetupCompleted": true
},
"tags": [
{
"name": "Claude Comments v2",
"id": "claude-comments-v2"
},
{
"name": "AI Automation",
"id": "ai-automation"
}
]
}
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 v2. Uses executeCommand, gitlab. Webhook trigger; 15 nodes.
Source: https://gitlab.com/huyhq8/gitlab-ai-automation/-/blob/develop/n8n/workflows/claude-comment-handler-v2.json — original creator credit. Request a take-down →