
Validate GitHub Configurations with AI and Log Issues to Sheets and Slack
Description
Categories
š DevOpsš¤ AI & Machine Learning
Nodes Used
n8n-nodes-base.coden8n-nodes-base.mergen8n-nodes-base.slackn8n-nodes-base.githubn8n-nodes-base.githubn8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.stickyNote
PriceFree
Views0
Last Updated11/28/2025
workflow.json
{
"id": "NDAfOVpefnu0AO5l",
"meta": {
"instanceId": "8443f10082278c46aa5cf3acf8ff0f70061a2c58bce76efac814b16290845177",
"templateCredsSetupCompleted": true
},
"name": "Validate GitHub Configurations with AI and Log Issues to Sheets and Slack",
"tags": [],
"nodes": [
{
"id": "00b8e7a5-b837-42dc-af23-9a2d06336a56",
"name": "Workflow Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1600,
-352
],
"parameters": {
"color": 4,
"width": 420,
"height": 520,
"content": "## š GitHub Config Validator\n\nAutomatically validates configuration consistency between repository configs and FAQ documentation using AI.\n\n**What it does:**\n- Monitors GitHub repo for push/PR events\n- Fetches config files and FAQ references\n- Uses AI to compare and detect mismatches\n- Logs discrepancies to Google Sheets\n- Sends Slack alerts for critical issues\n\n**Use Cases:**\n- DevOps config drift detection\n- Documentation accuracy validation\n- Automated compliance checking\n- Multi-environment config consistency\n\n**Business Value:**\n- Prevents production incidents from config drift\n- Ensures docs stay in sync with code\n- Reduces manual config review time\n- Maintains compliance standards"
},
"typeVersion": 1
},
{
"id": "22574d71-89eb-4872-a6ec-17127c7c13ac",
"name": "Setup Guide",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2000,
-336
],
"parameters": {
"color": 2,
"width": 380,
"height": 540,
"content": "## āļø Setup Instructions\n\n**Step 1: GitHub Configuration**\n- Create GitHub OAuth2 credential\n- Grant repo and webhook permissions\n- Configure webhook secret (optional)\n\n**Step 2: File Paths**\n- Config file: `config/app-config.json`\n- FAQ reference: `faq-config.json`\n- Adjust paths in nodes if different\n\n**Step 3: OpenAI Setup**\n- Add API key to credentials\n- Model: gpt-4o-mini ($0.03-0.05/run)\n- Max tokens: 4000\n\n**Step 4: Google Sheets**\n- Create sheet with columns (see below)\n- Connect OAuth2 credential\n\n**Step 5: Slack**\n- Add workspace OAuth2 token\n- Select notification channel\n- Customize alert template"
},
"typeVersion": 1
},
{
"id": "54dbfff3-2cfb-4e0f-92de-eeb8ad467ef4",
"name": "Trigger Configuration",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1152,
-432
],
"parameters": {
"color": 2,
"width": 320,
"height": 332,
"content": "## šÆ GitHub Webhook Trigger\n\n**Events monitored:**\n- `push`: Code commits\n- `pull_request`: PR opened/updated\n\n**Triggers when:**\nConfig files in monitored paths change\n\n**Webhook setup:**\n- Automatically created by n8n\n- Payload URL provided after activation\n- Add to GitHub repo settings\n\n**Security:**\n- Use webhook secret (recommended)\n- Validate payload signatures\n- Restrict to specific branches"
},
"typeVersion": 1
},
{
"id": "eabed35d-8708-4e30-9f50-de35746032b3",
"name": "File Fetching Logic",
"type": "n8n-nodes-base.stickyNote",
"position": [
-976,
176
],
"parameters": {
"color": 2,
"width": 300,
"height": 320,
"content": "## š File Fetching\n\n**Two parallel requests:**\n\n1. **Repository Config**\n - Path: `config/app-config.json`\n - Contains actual settings\n - Direct from main/master branch\n\n2. **FAQ Reference**\n - Path: `faq-config.json`\n - Documentation of expected config\n - Source of truth for validation\n\n**Both are JSON files** that get parsed and merged for AI comparison."
},
"typeVersion": 1
},
{
"id": "7a19688d-84bd-4c7c-98bd-6649a0192ed4",
"name": "Merge Strategy",
"type": "n8n-nodes-base.stickyNote",
"position": [
-512,
-480
],
"parameters": {
"color": 2,
"width": 280,
"height": 360,
"content": "## š Data Merging\n\n**Combines both configs:**\n- Repo config (actual)\n- FAQ config (expected)\n\n**Merge strategy:**\nCombines into single object for AI analysis\n\n**Output structure:**\n```json\n{\n \"repoConfig\": {...},\n \"faqConfig\": {...}\n}\n```\n\nReady for AI comparison"
},
"typeVersion": 1
},
{
"id": "a446599d-12b9-4438-8795-e2b93b9a026d",
"name": "AI Analysis Details",
"type": "n8n-nodes-base.stickyNote",
"position": [
-160,
-480
],
"parameters": {
"color": 2,
"width": 300,
"height": 372,
"content": "## š¤ AI Configuration Analysis\n\n**AI compares configs for:**\n- Mismatched values\n- Missing keys\n- Deprecated settings\n- Type inconsistencies\n- Security concerns\n\n**Severity Levels:**\n- `critical`: Breaking changes\n- `high`: Important mismatches\n- `medium`: Minor inconsistencies\n- `low`: Suggestions only\n\n**Output:**\nStructured JSON with issues array"
},
"typeVersion": 1
},
{
"id": "93354c1a-1de0-44ab-808f-cf9ddbcc1b07",
"name": "Data Processing",
"type": "n8n-nodes-base.stickyNote",
"position": [
208,
-448
],
"parameters": {
"color": 2,
"width": 300,
"height": 344,
"content": "## š Results Processing\n\n**Extracts from AI output:**\n- Issue ID\n- Config key with problem\n- Expected vs actual values\n- Issue type and severity\n- Actionable recommendations\n- Confidence score\n\n**Handles:**\n- Multiple issues per run\n- No issues found (success case)\n- Parsing errors gracefully\n\n**Adds metadata:**\n- Timestamp\n- Summary of all issues"
},
"typeVersion": 1
},
{
"id": "ee3b404c-ee6c-44d7-9123-c90b351f8160",
"name": "Sheets Configuration",
"type": "n8n-nodes-base.stickyNote",
"position": [
448,
64
],
"parameters": {
"color": 2,
"width": 320,
"height": 388,
"content": "## š Google Sheets Schema\n\n**Required columns:**\n- `timestamp`: When detected\n- `configKey`: Field name\n- `faqReference`: Expected value\n- `actualConfig`: Current value\n- `issueType`: Category\n- `severity`: Priority level\n- `suggestion`: Fix recommendation\n- `confidence`: AI confidence\n- `issueId`: Unique identifier\n- `summary`: Overall summary\n\n**Operation:** Append (logs history)\n"
},
"typeVersion": 1
},
{
"id": "133a9c1b-749f-4114-84ed-a23fd3e77d86",
"name": "Slack Alerts",
"type": "n8n-nodes-base.stickyNote",
"position": [
672,
-496
],
"parameters": {
"color": 2,
"width": 300,
"height": 408,
"content": "## š¬ Slack Notification\n\n**Alert includes:**\n- Config key with issue\n- Expected vs actual values\n- Issue type and severity\n- Actionable suggestion\n- Link to full report\n- Timestamp\n\n**Sent when:**\n- Any discrepancy detected\n- Critical/high severity issues\n\n**Customize:**\n- Channel selection\n- Message format\n- Severity filtering\n- @mention rules"
},
"typeVersion": 1
},
{
"id": "edf48d61-2eff-4e52-91c8-1ff181c99a4c",
"name": "GitHub Push or PR Event",
"type": "n8n-nodes-base.githubTrigger",
"position": [
-1088,
-96
],
"webhookId": "8269983f-8b5a-4f61-87a7-fb4e58e11b6c",
"parameters": {
"owner": {
"__rl": true,
"mode": "url",
"value": "={{ $json.githubOwner }}"
},
"events": [
"pull_request",
"push"
],
"options": {},
"repository": {
"__rl": true,
"mode": "id",
"value": "={{ $json.githubRepo }}"
},
"authentication": "oAuth2"
},
"credentials": {
"githubOAuth2Api": {
"id": "Ih8web2ia8dlggr6",
"name": "GitHub account vivek"
}
},
"typeVersion": 1
},
{
"id": "88bcabe0-4557-4f16-bba2-8e0523853fa3",
"name": "Fetch Repository Config",
"type": "n8n-nodes-base.github",
"position": [
-864,
-192
],
"webhookId": "9ad4450b-f494-4026-9ac9-d78c90169881",
"parameters": {
"owner": {
"__rl": true,
"mode": "url",
"value": "={{ $json.repository.owner.login }}"
},
"filePath": "config/app-config.json",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "id",
"value": "={{ $json.repository.name }}"
},
"authentication": "oAuth2",
"additionalParameters": {}
},
"credentials": {
"githubOAuth2Api": {
"id": "Ih8web2ia8dlggr6",
"name": "GitHub account vivek"
}
},
"typeVersion": 1
},
{
"id": "fe9f94a2-d9c9-4b51-832c-ae018daa821e",
"name": "Fetch FAQ Reference Config",
"type": "n8n-nodes-base.github",
"position": [
-864,
0
],
"webhookId": "b63918a7-17bc-4934-9007-3c8aa0b7aee9",
"parameters": {
"owner": {
"__rl": true,
"mode": "url",
"value": "={{ $json.repository.owner.login }}"
},
"filePath": "faq-config.json",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "id",
"value": "={{ $json.repository.name }}"
},
"authentication": "oAuth2",
"additionalParameters": {}
},
"credentials": {
"githubOAuth2Api": {
"id": "Ih8web2ia8dlggr6",
"name": "GitHub account vivek"
}
},
"typeVersion": 1
},
{
"id": "aaf24ba6-6338-4cd7-8451-b4add12fff46",
"name": "Parse Repo Config JSON",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-640,
-192
],
"parameters": {
"options": {},
"operation": "fromJson"
},
"typeVersion": 1
},
{
"id": "de433781-a62a-41e8-af75-6f659cc0bee6",
"name": "Parse FAQ Config JSON",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-640,
0
],
"parameters": {
"options": {},
"operation": "fromJson"
},
"typeVersion": 1
},
{
"id": "136b7b5e-c6c8-42db-ba9f-fea68d9e3db4",
"name": "Merge Config Files",
"type": "n8n-nodes-base.merge",
"position": [
-416,
-96
],
"parameters": {
"mode": "combine",
"options": {}
},
"typeVersion": 3
},
{
"id": "1e9f58bd-32a7-4652-a224-08a013ad656e",
"name": "AI Config Comparison Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-128,
-96
],
"parameters": {
"text": "=You are comparing two configuration files:\n\n**Repository Config (Actual):**\n```json\n{{ JSON.stringify($json[0], null, 2) }}\n```\n\n**FAQ Reference Config (Expected):**\n```json\n{{ JSON.stringify($json[1], null, 2) }}\n```\n\n**Task:**\nCompare both configs and identify all discrepancies. For each issue, provide:\n- Unique ID\n- Config key path\n- Expected value (from FAQ)\n- Actual value (from repo)\n- Issue type (mismatch, missing, deprecated, type_error, security)\n- Severity (critical, high, medium, low)\n- Clear recommendation\n- Confidence score (0.0-1.0)\n\n**Important:**\n- Return ONLY valid JSON (no markdown, no extra text)\n- Use the exact schema provided\n- If no issues found, return empty issues array\n- Focus on actionable discrepancies",
"options": {
"systemMessage": "You are an expert DevOps configuration auditor and automation specialist.\n\n**Your expertise:**\n- Deep knowledge of config file best practices\n- Security-first mindset for credentials and secrets\n- Understanding of environment-specific variations\n- Ability to distinguish critical vs cosmetic differences\n\n**Analysis approach:**\n1. Compare key-by-key between repo config and FAQ reference\n2. Identify missing keys, mismatched values, type changes\n3. Assess security implications (exposed secrets, weak settings)\n4. Determine business impact and urgency\n5. Provide specific, actionable recommendations\n\n**Severity guidelines:**\n- `critical`: Breaking changes, security vulnerabilities, missing required fields\n- `high`: Important mismatches affecting functionality\n- `medium`: Minor inconsistencies, deprecated but working\n- `low`: Cosmetic differences, optional improvements\n\n**Output requirements:**\n- Strict JSON format only\n- No markdown formatting\n- No explanatory text outside JSON\n- Clear, concise recommendations"
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2.1
},
{
"id": "f1dc1aff-caf8-4a0a-8c64-44fc69af9a22",
"name": "OpenAI GPT-4o-mini",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-192,
128
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "gpt-4o-mini"
},
"options": {
"maxTokens": 4000,
"temperature": 0.3
}
},
"credentials": {
"openAiApi": {
"id": "5Kzt6hGSZ1JHZqWN",
"name": "OpenAi account 2"
}
},
"typeVersion": 1.2
},
{
"id": "460f00a4-afff-4d0d-a922-cf6a865a5cd0",
"name": "JSON Output Schema",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
80,
128
],
"parameters": {
"jsonSchemaExample": "{\n \"result\": \"success or issues_found\",\n \"summary\": \"Brief overview of findings\",\n \"issues\": [\n {\n \"id\": \"unique_issue_id\",\n \"key\": \"config.path.to.field\",\n \"repo_value\": \"actual value in repository\",\n \"faq_value\": \"expected value from FAQ\",\n \"type\": \"mismatch | missing | deprecated | type_error | security\",\n \"severity\": \"critical | high | medium | low\",\n \"recommendation\": \"Specific action to resolve\",\n \"confidence\": 0.95\n }\n ],\n \"metadata\": {\n \"checked_at\": \"ISO timestamp\",\n \"comparison_source\": \"faq-config.json\"\n }\n}"
},
"typeVersion": 1.3
},
{
"id": "f50d6106-4931-46ce-b0ec-c816c1e25c8b",
"name": "Conversation Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
-64,
128
],
"parameters": {
"sessionKey": "=\"config_validation_\" & $('GitHub Push or PR Event').item.json.repository.name",
"sessionIdType": "customKey",
"contextWindowLength": 3
},
"typeVersion": 1.3
},
{
"id": "b40a6c22-c78e-43ba-90c6-2db71c74fe02",
"name": "Format Issues for Logging",
"type": "n8n-nodes-base.code",
"position": [
288,
-96
],
"parameters": {
"jsCode": "// Extract and format AI comparison results for Google Sheets\nconst allItems = $input.all();\nlet issues = [];\n\ntry {\n for (const item of allItems) {\n const aiResponse = item.json;\n \n // Handle different output structures\n let outputData;\n if (aiResponse.output) {\n outputData = [aiResponse.output];\n } else if (Array.isArray(aiResponse)) {\n outputData = aiResponse.map(entry => entry.output || entry);\n } else {\n outputData = [aiResponse];\n }\n\n // Process each output entry\n for (const output of outputData) {\n if (output.issues && Array.isArray(output.issues) && output.issues.length > 0) {\n \n // Map issues to Google Sheets format\n const mappedIssues = output.issues.map(issue => ({\n timestamp: new Date().toISOString(),\n configKey: issue.key || 'N/A',\n faqReference: issue.faq_value || 'N/A',\n actualConfig: issue.repo_value || 'N/A',\n issueType: issue.type || 'unknown',\n severity: issue.severity || 'N/A',\n suggestion: issue.recommendation || 'N/A',\n confidence: issue.confidence ? issue.confidence.toFixed(2) : 'N/A',\n issueId: issue.id || `issue_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,\n summary: output.summary || 'No summary provided'\n }));\n \n issues.push(...mappedIssues);\n }\n }\n }\n\n // Handle case when no issues found\n if (issues.length === 0) {\n issues = [{\n timestamp: new Date().toISOString(),\n configKey: 'No Issues',\n faqReference: 'N/A',\n actualConfig: 'N/A',\n issueType: 'success',\n severity: 'N/A',\n suggestion: 'All configurations match FAQ reference',\n confidence: '1.00',\n issueId: `success_${Date.now()}`,\n summary: 'ā
No discrepancies found - configs are in sync'\n }];\n }\n\n} catch (error) {\n // Error handling\n console.error('Error processing AI output:', error);\n issues = [{\n timestamp: new Date().toISOString(),\n configKey: 'Processing Error',\n faqReference: 'Could not parse AI response',\n actualConfig: error.message,\n issueType: 'error',\n severity: 'high',\n suggestion: `Review AI output manually. Error: ${error.message}`,\n confidence: 'N/A',\n issueId: `error_${Date.now()}`,\n summary: 'ā ļø Processing error occurred during validation'\n }];\n}\n\n// Return formatted data for Google Sheets\nreturn issues.map(issue => ({ json: issue }));"
},
"typeVersion": 2
},
{
"id": "be1fa800-8b48-4d21-a953-da7e3fc34eeb",
"name": "Log to Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
512,
-96
],
"parameters": {
"columns": {
"value": {},
"schema": [
{
"id": "timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "configKey",
"type": "string",
"display": true,
"required": false,
"displayName": "configKey",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "faqReference",
"type": "string",
"display": true,
"required": false,
"displayName": "faqReference",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "actualConfig",
"type": "string",
"display": true,
"required": false,
"displayName": "actualConfig",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "issueType",
"type": "string",
"display": true,
"required": false,
"displayName": "issueType",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "severity",
"type": "string",
"display": true,
"required": false,
"displayName": "severity",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "suggestion",
"type": "string",
"display": true,
"required": false,
"displayName": "suggestion",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "confidence",
"type": "string",
"display": true,
"required": false,
"displayName": "confidence",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "issueId",
"type": "string",
"display": true,
"required": false,
"displayName": "issueId",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "summary",
"type": "string",
"display": true,
"required": false,
"displayName": "summary",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "autoMapInputData",
"matchingColumns": []
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "Config Discrepancies"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Workflow Overview').item.json.googleSheetId }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "kpPEOLCGn963qpoh",
"name": "[email protected]"
}
},
"typeVersion": 4.7
},
{
"id": "4df1bf84-2753-4472-aef7-4623439bfcf0",
"name": "Send Slack Alert",
"type": "n8n-nodes-base.slack",
"position": [
736,
-96
],
"webhookId": "ef90cacf-14c2-48cc-905f-b06d2c649b8d",
"parameters": {
"text": "=ā ļø *Config Mismatch Detected!*\n\n*Repository:* {{ $('GitHub Push or PR Event').item.json.repository.full_name }}\n*Branch:* {{ $('GitHub Push or PR Event').item.json.ref }}\n*Commit:* {{ $('GitHub Push or PR Event').item.json.after.substring(0, 7) }}\n\nāāāāāāāāāāāāāāāāāāāā\n\n{{ $('Format Issues for Logging').all().slice(0, 3).map((item, idx) => {\n const severity = item.json.severity;\n const emoji = severity === 'critical' ? 'š“' : severity === 'high' ? 'š ' : severity === 'medium' ? 'š”' : 'š¢';\n return `${emoji} *Issue ${idx + 1}: ${item.json.configKey}*\\n⢠Type: ${item.json.issueType}\\n⢠Severity: ${item.json.severity}\\n⢠Expected: \\`${item.json.faqReference}\\`\\n⢠Actual: \\`${item.json.actualConfig}\\`\\n⢠Fix: ${item.json.suggestion}\\n`;\n}).join('\\n') }}\n\n*Total Issues:* {{ $('Format Issues for Logging').all().length }}\n\nš *Full Report:* <{{ $('Workflow Overview').item.json.sheetsReportUrl }}|View in Google Sheets>\n\n_Scanned: {{ new Date().toLocaleString('en-US', { timeZone: 'UTC' }) }} UTC_",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Workflow Overview').item.json.slackChannel }}"
},
"otherOptions": {}
},
"credentials": {
"slackApi": {
"id": "rNqvWj9TfChPVRYY",
"name": "Slack account vivek"
}
},
"typeVersion": 2.2
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "f61bd7be-e6be-425b-8929-bc5536255b8f",
"connections": {
"JSON Output Schema": {
"ai_outputParser": [
[
{
"node": "AI Config Comparison Agent",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Merge Config Files": {
"main": [
[
{
"node": "AI Config Comparison Agent",
"type": "main",
"index": 0
}
]
]
},
"OpenAI GPT-4o-mini": {
"ai_languageModel": [
[
{
"node": "AI Config Comparison Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Conversation Memory": {
"ai_memory": [
[
{
"node": "AI Config Comparison Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Log to Google Sheets": {
"main": [
[
{
"node": "Send Slack Alert",
"type": "main",
"index": 0
}
]
]
},
"Parse FAQ Config JSON": {
"main": [
[
{
"node": "Merge Config Files",
"type": "main",
"index": 1
}
]
]
},
"Parse Repo Config JSON": {
"main": [
[
{
"node": "Merge Config Files",
"type": "main",
"index": 0
}
]
]
},
"Fetch Repository Config": {
"main": [
[
{
"node": "Parse Repo Config JSON",
"type": "main",
"index": 0
}
]
]
},
"GitHub Push or PR Event": {
"main": [
[
{
"node": "Fetch Repository Config",
"type": "main",
"index": 0
},
{
"node": "Fetch FAQ Reference Config",
"type": "main",
"index": 0
}
]
]
},
"Format Issues for Logging": {
"main": [
[
{
"node": "Log to Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"AI Config Comparison Agent": {
"main": [
[
{
"node": "Format Issues for Logging",
"type": "main",
"index": 0
}
]
]
},
"Fetch FAQ Reference Config": {
"main": [
[
{
"node": "Parse FAQ Config JSON",
"type": "main",
"index": 0
}
]
]
}
}
}