This workflow corresponds to n8n.io template #18094 — we link there as the canonical source.
This workflow follows the Agent → HTTP Request recipe pattern — see all workflows that pair these two integrations.
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 →
{
"id": "lksnzgZa5lk6KKLZ",
"name": "Cloud-Cost Anomaly Root-Cause Auto-PR Agent",
"tags": [],
"nodes": [
{
"id": "6f870743-aeaf-4279-ba3c-e6d8c605b589",
"name": "Sticky Note - Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1120,
112
],
"parameters": {
"width": 1020,
"height": 1180,
"content": "## Cloud-Cost Anomaly Root-Cause Auto-PR Agent\n\nFor DevOps/SRE teams. This workflow polls AWS Cost Explorer for daily spend, detects a cost spike against a rolling baseline, finds the most recent Terraform commit that touched the relevant infra path, asks AI to diagnose the likely root cause and draft a fix, opens a real GitHub Pull Request with that fix on a new branch, and notifies the team in Slack \u2014 fully autonomous, end to end.\n\n### How it works\n1. Runs on a schedule (or manually for testing)\n2. Fetches AWS Cost Explorer data (daily, grouped by service) and computes today's cost vs a rolling baseline average\n3. If the increase clears the configured threshold, it's flagged as a spike, with the top contributing AWS service identified\n4. Lists recent GitHub commits that touched the Terraform path, picks the most recent as the prime suspect\n5. Fetches the current Terraform file content\n6. AI analyzes the spike data, suspect commit, and file content, and produces: a root-cause summary, a suggested fix, a fully corrected file, and PR metadata\n7. Creates a new branch from the base branch, commits the corrected file to it, opens a Pull Request\n8. Posts a Slack message with the cost numbers, root cause, fix summary, and PR link\n\n### How to set up\n1. Import this workflow into n8n\n2. Add an AWS credential (with Cost Explorer / ce:GetCostAndUsage permission) to the 'Fetch AWS Cost & Usage' node\n3. Add a GitHub API credential (personal access token with repo scope) to all GitHub HTTP Request nodes\n4. Add an OpenAI credential to the OpenAI Chat Model node\n5. Add a Slack credential to the Slack node and pick/verify the target channel\n6. Edit the Set - Config node: repoOwner, repoName, terraformPath, baseBranch, costSpikeThresholdPercent, slackChannel\n7. Activate the workflow, or use the Manual Trigger to test end to end\n\n### Requirements\n- AWS credentials with Cost Explorer read access (Cost Explorer API only runs in us-east-1)\n- GitHub personal access token with repo (and workflow if protected branches) scope\n- OpenAI API key (GPT-4.1-mini or above)\n- Slack app/bot token with chat:write scope\n\n### How to customize\n- Adjust costSpikeThresholdPercent and the baseline lookback window in Set - Config and JS - Parse Cost Data & Detect Spike\n- Point terraformPath at any file (EC2 module, ECS service, RDS instance, etc.) \u2014 or run multiple copies of this workflow, one per critical file\n- Swap the Slack node for Microsoft Teams / PagerDuty for on-call routing on High-severity spikes\n- Require human approval before merge by leaving the PR open (default) instead of auto-merging"
},
"typeVersion": 1
},
{
"id": "6f478a92-b625-4a5a-aaa2-85f3609fdeca",
"name": "Sticky Note - Detection",
"type": "n8n-nodes-base.stickyNote",
"position": [
80,
272
],
"parameters": {
"color": 4,
"width": 884,
"height": 900,
"content": "## 1. Trigger & Cost Spike Detection\n\nSet - Config centralizes all the knobs: repo, Terraform path, base branch, spike threshold, Slack channel, and the computed date ranges used by the AWS Cost Explorer call.\n\nHTTP Request - Fetch AWS Cost & Usage calls the Cost Explorer GetCostAndUsage endpoint (daily granularity, grouped by SERVICE) for the last 8 days.\n\nJS - Parse Cost Data & Detect Spike computes today's total cost vs the average of the prior days (the baseline), the percent increase, and which single AWS service contributed the most to the increase. IF - Cost Spike Detected gates everything downstream on that threshold."
},
"typeVersion": 1
},
{
"id": "f91691da-1ae3-4281-8755-9229966b76de",
"name": "Sticky Note - Root Cause",
"type": "n8n-nodes-base.stickyNote",
"position": [
1056,
256
],
"parameters": {
"color": 3,
"width": 1364,
"height": 900,
"content": "## 2. Root-Cause Commit & AI Diagnosis\n\nHTTP Request - List Terraform Commits pulls recent commits that touched the configured Terraform path. JS - Identify Root-Cause Commit picks the most recent one as the prime suspect (GitHub returns commits newest-first).\n\nHTTP Request - Get Current Terraform File fetches the live file content (base64) so the AI has ground truth to work from.\n\nAI - Generate Root Cause Analysis & Terraform Fix reads the cost numbers, the suspect commit, and the current file, and returns a root-cause summary, a plain-English fix description, and a complete corrected file, plus PR branch/title/body/commit message."
},
"typeVersion": 1
},
{
"id": "07cc19a3-ab13-4b61-9c06-9fef05929f12",
"name": "Sticky Note - PR & Notify",
"type": "n8n-nodes-base.stickyNote",
"position": [
2448,
208
],
"parameters": {
"color": 5,
"width": 1648,
"height": 900,
"content": "## 3. Branch, Commit, PR & Slack Notify\n\nJS - Parse AI Fix Output & Build Payloads safely parses the AI's JSON, base64-encodes the corrected file, and carries the current file's blob sha forward (required by GitHub's contents API to update a file).\n\nHTTP Request - Get Base Branch Ref reads the base branch's current commit sha, then HTTP Request - Create Branch creates a new branch from it. HTTP Request - Update Terraform File on Branch commits the AI's fix to that branch, and HTTP Request - Create Pull Request opens the PR against the base branch.\n\nFinally, the Slack node posts the cost numbers, root cause, fix summary, and the live PR link to the configured channel. Every GitHub/Slack call uses continueOnFail so a downstream API hiccup doesn't kill the run."
},
"typeVersion": 1
},
{
"id": "0b6afc17-1356-45a4-97d2-85a47652a2c4",
"name": "Schedule Trigger - Every 6 Hours",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
176,
720
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 */6 * * *"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "43af63c0-4ced-41d2-978a-bdf90a0e740d",
"name": "Manual Trigger - Test Run",
"type": "n8n-nodes-base.manualTrigger",
"position": [
176,
912
],
"parameters": {},
"typeVersion": 1
},
{
"id": "3b066b58-d1e3-4664-bfce-c4e29527bf93",
"name": "Set - Config",
"type": "n8n-nodes-base.set",
"position": [
400,
800
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"name": "repoOwner",
"type": "string",
"value": "your-org"
},
{
"name": "repoName",
"type": "string",
"value": "infra-terraform"
},
{
"name": "terraformPath",
"type": "string",
"value": "environments/prod/main.tf"
},
{
"name": "baseBranch",
"type": "string",
"value": "main"
},
{
"name": "costSpikeThresholdPercent",
"type": "number",
"value": 30
},
{
"name": "slackChannel",
"type": "string",
"value": "#cloud-cost-alerts"
},
{
"name": "costStartDate",
"type": "string",
"value": "={{ $now.minus({ days: 8 }).toFormat('yyyy-LL-dd') }}"
},
{
"name": "costEndDate",
"type": "string",
"value": "={{ $now.plus({ days: 1 }).toFormat('yyyy-LL-dd') }}"
},
{
"name": "commitsSinceDate",
"type": "string",
"value": "={{ $now.minus({ days: 14 }).toISO() }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "3f946e3d-505e-4bae-8f8d-f5da65f78045",
"name": "HTTP Request - Fetch AWS Cost & Usage",
"type": "n8n-nodes-base.httpRequest",
"position": [
624,
800
],
"parameters": {
"url": "https://ce.us-east-1.amazonaws.com/",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ TimePeriod: { Start: $json.costStartDate, End: $json.costEndDate }, Granularity: 'DAILY', Metrics: ['UnblendedCost'], GroupBy: [{ Type: 'DIMENSION', Key: 'SERVICE' }] }) }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "X-Amz-Target",
"value": "AWSInsightsIndexService.GetCostAndUsage"
},
{
"name": "Content-Type",
"value": "application/x-amz-json-1.1"
}
]
},
"nodeCredentialType": "aws"
},
"credentials": {
"aws": {
"name": "<your credential>"
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "cf15f082-d2f4-4e0f-be7c-0601c0efdcc6",
"name": "JS - Parse Cost Data & Detect Spike",
"type": "n8n-nodes-base.code",
"position": [
848,
800
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Parse AWS Cost Explorer response and detect a cost spike vs the recent baseline\nconst response = $input.item.json;\nconst resultsByTime = response.ResultsByTime || [];\nconst config = $('Set - Config').item.json;\n\nif (resultsByTime.length < 2) {\n return { json: { ...config, isSpike: false, reason: 'insufficient_cost_data' } };\n}\n\nconst daily = resultsByTime.map(r => {\n let dayTotal = 0;\n if (r.Groups && r.Groups.length) {\n dayTotal = r.Groups.reduce((sum, g) => sum + parseFloat(g.Metrics.UnblendedCost.Amount), 0);\n } else if (r.Total && r.Total.UnblendedCost) {\n dayTotal = parseFloat(r.Total.UnblendedCost.Amount);\n }\n return { date: r.TimePeriod.Start, total: dayTotal, groups: r.Groups || [] };\n});\n\nconst todayEntry = daily[daily.length - 1];\nconst baseline = daily.slice(0, -1);\nconst avgBaseline = baseline.reduce((s, d) => s + d.total, 0) / baseline.length;\n\nconst percentIncrease = avgBaseline > 0 ? ((todayEntry.total - avgBaseline) / avgBaseline) * 100 : 0;\n\nconst serviceTotalsToday = {};\n(todayEntry.groups || []).forEach(g => {\n const svc = g.Keys[0];\n serviceTotalsToday[svc] = parseFloat(g.Metrics.UnblendedCost.Amount);\n});\n\nconst serviceBaselineAvg = {};\nbaseline.forEach(d => {\n (d.groups || []).forEach(g => {\n const svc = g.Keys[0];\n const amt = parseFloat(g.Metrics.UnblendedCost.Amount);\n serviceBaselineAvg[svc] = (serviceBaselineAvg[svc] || 0) + amt;\n });\n});\nObject.keys(serviceBaselineAvg).forEach(svc => {\n serviceBaselineAvg[svc] = serviceBaselineAvg[svc] / baseline.length;\n});\n\nlet topService = null, topServiceIncreaseAmount = 0, topServiceIncreasePercent = 0;\nObject.keys(serviceTotalsToday).forEach(svc => {\n const baselineAmt = serviceBaselineAvg[svc] || 0;\n const increaseAmount = serviceTotalsToday[svc] - baselineAmt;\n if (increaseAmount > topServiceIncreaseAmount) {\n topServiceIncreaseAmount = increaseAmount;\n topService = svc;\n topServiceIncreasePercent = baselineAmt > 0 ? (increaseAmount / baselineAmt) * 100 : 100;\n }\n});\n\nreturn {\n json: {\n ...config,\n todayCost: Math.round(todayEntry.total * 100) / 100,\n avgBaselineCost: Math.round(avgBaseline * 100) / 100,\n percentIncrease: Math.round(percentIncrease * 100) / 100,\n topService,\n topServiceIncreaseAmount: Math.round(topServiceIncreaseAmount * 100) / 100,\n topServiceIncreasePercent: Math.round(topServiceIncreasePercent * 100) / 100,\n isSpike: percentIncrease >= parseFloat(config.costSpikeThresholdPercent)\n }\n};"
},
"typeVersion": 2
},
{
"id": "4bcdb90e-f01c-4c67-a514-a6612a870602",
"name": "IF - Cost Spike Detected",
"type": "n8n-nodes-base.if",
"position": [
1072,
800
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "boolean",
"operation": "true"
},
"leftValue": "={{ $json.isSpike }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "3f658f7f-5e17-481b-865f-d84c9e4f3738",
"name": "HTTP Request - List Terraform Commits",
"type": "n8n-nodes-base.httpRequest",
"position": [
1360,
720
],
"parameters": {
"url": "=https://api.github.com/repos/{{ $json.repoOwner }}/{{ $json.repoName }}/commits?path={{ $json.terraformPath }}&sha={{ $json.baseBranch }}&since={{ $json.commitsSinceDate }}",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi"
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "43551c78-9dad-43db-b5cf-3d7355f8141b",
"name": "JS - Identify Root-Cause Commit",
"type": "n8n-nodes-base.code",
"position": [
1584,
720
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// GitHub returns commits newest-first; the most recent commit touching the\n// Terraform path before the spike is the prime suspect for the root cause\nconst commits = $input.item.json;\nconst context = $('JS - Parse Cost Data & Detect Spike').item.json;\n\nif (!Array.isArray(commits) || commits.length === 0) {\n return { json: { ...context, suspectCommitFound: false } };\n}\n\nconst top = commits[0];\n\nreturn {\n json: {\n ...context,\n suspectCommitFound: true,\n suspectCommitSha: top.sha,\n suspectCommitMessage: top.commit.message,\n suspectCommitAuthor: top.commit.author.name,\n suspectCommitDate: top.commit.author.date,\n suspectCommitUrl: top.html_url\n }\n};"
},
"typeVersion": 2
},
{
"id": "c83e2d88-940e-482b-bf4a-b68aa497a1e0",
"name": "HTTP Request - Get Current Terraform File",
"type": "n8n-nodes-base.httpRequest",
"position": [
1808,
720
],
"parameters": {
"url": "=https://api.github.com/repos/{{ $json.repoOwner }}/{{ $json.repoName }}/contents/{{ $json.terraformPath }}?ref={{ $json.baseBranch }}",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi"
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "f40ca19f-6714-4502-a8ce-f04b2cbcb016",
"name": "AI - Generate Root Cause Analysis & Terraform Fix",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1984,
720
],
"parameters": {
"text": "=You are a cloud cost optimization and Terraform expert. A cost spike was detected and a suspect commit has been identified.\n\nCost Spike:\n- Today's total cost: ${{ $('JS - Identify Root-Cause Commit').item.json.todayCost }}\n- Baseline average: ${{ $('JS - Identify Root-Cause Commit').item.json.avgBaselineCost }}\n- Percent increase: {{ $('JS - Identify Root-Cause Commit').item.json.percentIncrease }}%\n- Top contributing service: {{ $('JS - Identify Root-Cause Commit').item.json.topService }} (+${{ $('JS - Identify Root-Cause Commit').item.json.topServiceIncreaseAmount }}, {{ $('JS - Identify Root-Cause Commit').item.json.topServiceIncreasePercent }}%)\n\nSuspect Commit:\n- SHA: {{ $('JS - Identify Root-Cause Commit').item.json.suspectCommitSha }}\n- Message: {{ $('JS - Identify Root-Cause Commit').item.json.suspectCommitMessage }}\n- Author: {{ $('JS - Identify Root-Cause Commit').item.json.suspectCommitAuthor }}\n- Date: {{ $('JS - Identify Root-Cause Commit').item.json.suspectCommitDate }}\n\nCurrent Terraform file ({{ $('JS - Identify Root-Cause Commit').item.json.terraformPath }}), base64-encoded content:\n{{ $json.content }}\n\nDecode the base64 content mentally, review it for likely cost drivers tied to the top contributing service (oversized instance types, high desired_count/min/max, over-provisioned storage or throughput, missing lifecycle rules, orphaned NAT gateways, etc.), and:\n1. Write a rootCauseSummary (2-3 sentences) explaining the likely cause.\n2. Write a suggestedFixDescription (2-4 sentences) explaining the proposed change.\n3. Produce updatedFileContent: the COMPLETE corrected file content (plain text, not base64) with a conservative, safe fix applied \u2014 keep all unrelated content unchanged.\n4. prBranchName: a short kebab-case branch name.\n5. prTitle: a concise PR title.\n6. prBody: a markdown PR description including the root cause, the fix, and the cost impact figures above.\n7. commitMessage: a concise commit message.\n\nReturn ONLY a JSON object with keys: rootCauseSummary, suggestedFixDescription, updatedFileContent, prBranchName, prTitle, prBody, commitMessage. No markdown fences, no commentary.",
"options": {},
"promptType": "define"
},
"typeVersion": 1.6
},
{
"id": "d035ad64-e25f-4cc6-a111-0275eba5aa75",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
2032,
944
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {},
"builtInTools": {}
},
"typeVersion": 1.3
},
{
"id": "37878c4d-d423-427f-91d0-aba4fdf360be",
"name": "JS - Parse AI Fix Output & Build Payloads",
"type": "n8n-nodes-base.code",
"position": [
2256,
720
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Parse the AI's structured fix output and prepare GitHub API payloads\nconst item = $input.item.json;\nconst raw = item.output || item.text || item.response || '{}';\n\nlet parsed = {};\ntry {\n const cleaned = String(raw).replace(/```json|```/g, '').trim();\n parsed = JSON.parse(cleaned);\n} catch (e) {\n parsed = {\n rootCauseSummary: 'AI response could not be parsed automatically. Manual review required.',\n suggestedFixDescription: '',\n updatedFileContent: null,\n prBranchName: 'cost-anomaly-fix-' + Date.now(),\n prTitle: 'Investigate cloud cost anomaly',\n prBody: String(raw),\n commitMessage: 'Investigate cloud cost anomaly'\n };\n}\n\nconst context = $('JS - Identify Root-Cause Commit').item.json;\nconst fileInfo = $('HTTP Request - Get Current Terraform File').item.json;\n\nreturn {\n json: {\n ...context,\n rootCauseSummary: parsed.rootCauseSummary || '',\n suggestedFixDescription: parsed.suggestedFixDescription || '',\n updatedFileContentBase64: parsed.updatedFileContent ? Buffer.from(parsed.updatedFileContent, 'utf-8').toString('base64') : null,\n prBranchName: parsed.prBranchName || ('cost-anomaly-fix-' + Date.now()),\n prTitle: parsed.prTitle || ('Fix cost anomaly in ' + context.terraformPath),\n prBody: parsed.prBody || parsed.rootCauseSummary || '',\n commitMessage: parsed.commitMessage || ('Fix cost anomaly in ' + context.terraformPath),\n currentFileSha: fileInfo.sha,\n hasFix: !!parsed.updatedFileContent\n }\n};"
},
"typeVersion": 2
},
{
"id": "158f361a-17f0-456c-a53d-5a206312ecd6",
"name": "HTTP Request - Get Base Branch Ref",
"type": "n8n-nodes-base.httpRequest",
"position": [
2480,
720
],
"parameters": {
"url": "=https://api.github.com/repos/{{ $json.repoOwner }}/{{ $json.repoName }}/git/ref/heads/{{ $json.baseBranch }}",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi"
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "8aacbb31-22a5-4912-aab0-b35810db1b0b",
"name": "HTTP Request - Create Branch",
"type": "n8n-nodes-base.httpRequest",
"position": [
2704,
720
],
"parameters": {
"url": "=https://api.github.com/repos/{{ $('JS - Parse AI Fix Output & Build Payloads').item.json.repoOwner }}/{{ $('JS - Parse AI Fix Output & Build Payloads').item.json.repoName }}/git/refs",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ ref: 'refs/heads/' + $('JS - Parse AI Fix Output & Build Payloads').item.json.prBranchName, sha: $json.object.sha }) }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi"
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "a4b26f6b-7cb1-4418-9079-de8636e786aa",
"name": "HTTP Request - Update Terraform File on Branch",
"type": "n8n-nodes-base.httpRequest",
"position": [
3136,
720
],
"parameters": {
"url": "=https://api.github.com/repos/{{ $('JS - Parse AI Fix Output & Build Payloads').item.json.repoOwner }}/{{ $('JS - Parse AI Fix Output & Build Payloads').item.json.repoName }}/contents/{{ $('JS - Parse AI Fix Output & Build Payloads').item.json.terraformPath }}",
"method": "PUT",
"options": {},
"jsonBody": "={{ JSON.stringify({ message: $('JS - Parse AI Fix Output & Build Payloads').item.json.commitMessage, content: $('JS - Parse AI Fix Output & Build Payloads').item.json.updatedFileContentBase64, sha: $('JS - Parse AI Fix Output & Build Payloads').item.json.currentFileSha, branch: $('JS - Parse AI Fix Output & Build Payloads').item.json.prBranchName }) }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi"
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "a938b1e5-6de0-4eb2-9e81-3c238fe3d842",
"name": "HTTP Request - Create Pull Request",
"type": "n8n-nodes-base.httpRequest",
"position": [
3360,
720
],
"parameters": {
"url": "=https://api.github.com/repos/{{ $('JS - Parse AI Fix Output & Build Payloads').item.json.repoOwner }}/{{ $('JS - Parse AI Fix Output & Build Payloads').item.json.repoName }}/pulls",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ title: $('JS - Parse AI Fix Output & Build Payloads').item.json.prTitle, head: $('JS - Parse AI Fix Output & Build Payloads').item.json.prBranchName, base: $('JS - Parse AI Fix Output & Build Payloads').item.json.baseBranch, body: $('JS - Parse AI Fix Output & Build Payloads').item.json.prBody }) }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi"
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "ac9b6c08-8d5a-408c-a699-c492332e9c14",
"name": "Slack - Notify Team",
"type": "n8n-nodes-base.slack",
"position": [
3584,
720
],
"parameters": {
"text": "=\ud83d\udea8 *Cloud Cost Anomaly Detected \u2014 Auto-PR Created*\n\n*Service:* {{ $('JS - Parse AI Fix Output & Build Payloads').item.json.topService }}\n*Cost increase:* {{ $('JS - Parse AI Fix Output & Build Payloads').item.json.percentIncrease }}% (today: ${{ $('JS - Parse AI Fix Output & Build Payloads').item.json.todayCost }} vs baseline ${{ $('JS - Parse AI Fix Output & Build Payloads').item.json.avgBaselineCost }})\n*Suspect commit:* {{ $('JS - Parse AI Fix Output & Build Payloads').item.json.suspectCommitSha }} \u2014 {{ $('JS - Parse AI Fix Output & Build Payloads').item.json.suspectCommitMessage }}\n*Root cause:* {{ $('JS - Parse AI Fix Output & Build Payloads').item.json.rootCauseSummary }}\n*Fix:* {{ $('JS - Parse AI Fix Output & Build Payloads').item.json.suggestedFixDescription }}\n*Pull Request:* {{ $json.html_url }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "={{ $('JS - Parse AI Fix Output & Build Payloads').item.json.slackChannel }}"
},
"otherOptions": {}
},
"typeVersion": 2.3,
"continueOnFail": true
},
{
"id": "b8c029dc-8b29-40e5-96ee-28ac4112ae0a",
"name": "Wait For Result",
"type": "n8n-nodes-base.wait",
"position": [
2912,
720
],
"parameters": {},
"typeVersion": 1.1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "8a1155b4-c47d-4da5-9c67-76eb9047b997",
"nodeGroups": [],
"connections": {
"Set - Config": {
"main": [
[
{
"node": "HTTP Request - Fetch AWS Cost & Usage",
"type": "main",
"index": 0
}
]
]
},
"Wait For Result": {
"main": [
[
{
"node": "HTTP Request - Update Terraform File on Branch",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI - Generate Root Cause Analysis & Terraform Fix",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"IF - Cost Spike Detected": {
"main": [
[
{
"node": "HTTP Request - List Terraform Commits",
"type": "main",
"index": 0
}
]
]
},
"Manual Trigger - Test Run": {
"main": [
[
{
"node": "Set - Config",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request - Create Branch": {
"main": [
[
{
"node": "Wait For Result",
"type": "main",
"index": 0
}
]
]
},
"JS - Identify Root-Cause Commit": {
"main": [
[
{
"node": "HTTP Request - Get Current Terraform File",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger - Every 6 Hours": {
"main": [
[
{
"node": "Set - Config",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request - Create Pull Request": {
"main": [
[
{
"node": "Slack - Notify Team",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request - Get Base Branch Ref": {
"main": [
[
{
"node": "HTTP Request - Create Branch",
"type": "main",
"index": 0
}
]
]
},
"JS - Parse Cost Data & Detect Spike": {
"main": [
[
{
"node": "IF - Cost Spike Detected",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request - Fetch AWS Cost & Usage": {
"main": [
[
{
"node": "JS - Parse Cost Data & Detect Spike",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request - List Terraform Commits": {
"main": [
[
{
"node": "JS - Identify Root-Cause Commit",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request - Get Current Terraform File": {
"main": [
[
{
"node": "AI - Generate Root Cause Analysis & Terraform Fix",
"type": "main",
"index": 0
}
]
]
},
"JS - Parse AI Fix Output & Build Payloads": {
"main": [
[
{
"node": "HTTP Request - Get Base Branch Ref",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request - Update Terraform File on Branch": {
"main": [
[
{
"node": "HTTP Request - Create Pull Request",
"type": "main",
"index": 0
}
]
]
},
"AI - Generate Root Cause Analysis & Terraform Fix": {
"main": [
[
{
"node": "JS - Parse AI Fix Output & Build Payloads",
"type": "main",
"index": 0
}
]
]
}
}
}
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.
awsgithubApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs every 6 hours to detect AWS Cost Explorer spend spikes, correlates the spike to the latest Terraform change in GitHub, uses OpenAI to draft a root-cause analysis and updated Terraform file, opens a GitHub pull request with the fix, and posts the results to…
Source: https://n8n.io/workflows/18094/ — 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.
Created by: Peyton Leveillee Last updated: October 2025
Runs automatically every Monday morning at 8 AM Collects your Google Search Console from the last month and the month before that for a given url (date range is configurable) Formats the data, aggrega
This workflow automates end-to-end sustainability lifecycle management for corporate sustainability teams, ESG governance officers, and circular economy programme leads. It addresses the challenge of
Marketing, content, and enablement teams that need a quick, human-readable summary of every new video published by the YouTube channels they care about—without leaving Slack.
This workflow automates end-to-end ESG (Environmental, Social, and Governance) sustainability reporting for enterprise sustainability teams, compliance officers, and green governance leads. It solves