All checks were successful
Auto-merge docs-only PRs / auto-merge-docs (pull_request) Successful in 2s
- runner.sh: opt-in session persistence via session_resumable and resume_last_session settings; fix read_setting to normalize booleans - issue-poller.sh: capture and log session_id from worker invocations, include in result JSON - pr-reviewer-dispatcher.sh: capture and log session_id from reviews - n8n workflow: add --append-system-prompt to initial SSH node, add Follow Up Diagnostics node using --resume for deeper investigation, update Discord Alert with remediation details - Add Agent SDK evaluation doc (CLI vs Python/TS SDK comparison) - Update CONTEXT.md with session resumption documentation Closes #3 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
221 lines
11 KiB
JSON
221 lines
11 KiB
JSON
{
|
|
"name": "Server Health Monitor - Claude Code",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"rule": {
|
|
"interval": [
|
|
{
|
|
"field": "minutes",
|
|
"minutesInterval": 5
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"id": "schedule-trigger",
|
|
"name": "Every 5 Minutes",
|
|
"type": "n8n-nodes-base.scheduleTrigger",
|
|
"typeVersion": 1.2,
|
|
"position": [0, 0]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "executeCommand",
|
|
"command": "/root/.local/bin/claude -p \"Run python3 ~/.claude/skills/server-diagnostics/client.py health paper-dynasty and analyze the results. If any containers are not running or there are critical issues, summarize them. Otherwise just say 'All systems healthy'.\" --output-format json --json-schema '{\"type\":\"object\",\"properties\":{\"status\":{\"type\":\"string\",\"enum\":[\"healthy\",\"issues_found\"]},\"summary\":{\"type\":\"string\"},\"root_cause\":{\"type\":\"string\"},\"severity\":{\"type\":\"string\",\"enum\":[\"low\",\"medium\",\"high\",\"critical\"]},\"affected_services\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"actions_taken\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"status\",\"severity\",\"summary\"]}' --allowedTools \"Read,Grep,Glob,Bash(python3 ~/.claude/skills/server-diagnostics/client.py *)\" --append-system-prompt \"You are a server diagnostics agent. Use the server-diagnostics skill client.py for all operations. Never run destructive commands.\"",
|
|
"options": {}
|
|
},
|
|
"id": "ssh-claude-code",
|
|
"name": "Run Claude Diagnostics",
|
|
"type": "n8n-nodes-base.ssh",
|
|
"typeVersion": 1,
|
|
"position": [220, 0],
|
|
"credentials": {
|
|
"sshPassword": {
|
|
"id": "REPLACE_WITH_CREDENTIAL_ID",
|
|
"name": "Claude Code LXC"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Parse Claude Code JSON output (uses --json-schema for structured_output)\nconst stdout = $input.first().json.stdout || '';\n\ntry {\n const response = JSON.parse(stdout);\n const data = response.structured_output || JSON.parse(response.result || '{}');\n \n return [{\n json: {\n success: !response.is_error,\n status: data.status || 'unknown',\n summary: data.summary || response.result || 'No result',\n severity: data.severity || 'low',\n root_cause: data.root_cause || null,\n affected_services: data.affected_services || [],\n actions_taken: data.actions_taken || [],\n duration_ms: response.duration_ms,\n cost_usd: response.total_cost_usd,\n session_id: response.session_id,\n has_issues: data.status === 'issues_found',\n raw: response\n }\n }];\n} catch (e) {\n return [{\n json: {\n success: false,\n status: 'error',\n summary: 'Failed to parse Claude response',\n severity: 'high',\n error: e.message,\n raw_stdout: stdout,\n has_issues: true\n }\n }];\n}"
|
|
},
|
|
"id": "parse-response",
|
|
"name": "Parse Claude Response",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [440, 0]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict"
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "check-issues",
|
|
"leftValue": "={{ $json.has_issues }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "check-issues",
|
|
"name": "Has Issues?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2,
|
|
"position": [660, 0]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "executeCommand",
|
|
"command": "=/root/.local/bin/claude -p \"The previous health check found issues. Investigate deeper: check container logs, resource usage, and recent events. Provide a detailed root cause analysis and recommended remediation steps.\" --resume \"{{ $json.session_id }}\" --output-format json --json-schema '{\"type\":\"object\",\"properties\":{\"root_cause_detail\":{\"type\":\"string\"},\"container_logs\":{\"type\":\"string\"},\"resource_status\":{\"type\":\"string\"},\"remediation_steps\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"requires_human\":{\"type\":\"boolean\"}},\"required\":[\"root_cause_detail\",\"remediation_steps\",\"requires_human\"]}' --allowedTools \"Read,Grep,Glob,Bash(python3 ~/.claude/skills/server-diagnostics/client.py *)\" --max-turns 15 --append-system-prompt \"You are a server diagnostics agent performing a follow-up investigation. The initial health check found issues. Dig deeper into logs and metrics. Never run destructive commands.\"",
|
|
"options": {}
|
|
},
|
|
"id": "ssh-followup",
|
|
"name": "Follow Up Diagnostics",
|
|
"type": "n8n-nodes-base.ssh",
|
|
"typeVersion": 1,
|
|
"position": [880, -200],
|
|
"credentials": {
|
|
"sshPassword": {
|
|
"id": "REPLACE_WITH_CREDENTIAL_ID",
|
|
"name": "Claude Code LXC"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Parse follow-up diagnostics response\nconst stdout = $input.first().json.stdout || '';\nconst initial = $('Parse Claude Response').first().json;\n\ntry {\n const response = JSON.parse(stdout);\n const data = response.structured_output || JSON.parse(response.result || '{}');\n \n return [{\n json: {\n ...initial,\n followup: {\n root_cause_detail: data.root_cause_detail || 'No detail available',\n container_logs: data.container_logs || '',\n resource_status: data.resource_status || '',\n remediation_steps: data.remediation_steps || [],\n requires_human: data.requires_human || false,\n cost_usd: response.total_cost_usd,\n session_id: response.session_id\n },\n total_cost_usd: (initial.cost_usd || 0) + (response.total_cost_usd || 0)\n }\n }];\n} catch (e) {\n return [{\n json: {\n ...initial,\n followup: {\n error: e.message,\n root_cause_detail: 'Follow-up parse failed',\n remediation_steps: [],\n requires_human: true\n },\n total_cost_usd: initial.cost_usd || 0\n }\n }];\n}"
|
|
},
|
|
"id": "parse-followup",
|
|
"name": "Parse Follow-up Response",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [1100, -200]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://discord.com/api/webhooks/1451783909409816763/O9PMDiNt6ZIWRf8HKocIZ_E4vMGV_lEwq50aAiZ9HVFR2UGwO6J1N9_wOm82p0MetIqT",
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={\n \"embeds\": [{\n \"title\": \"{{ $json.severity === 'critical' ? '🔴' : $json.severity === 'high' ? '🟠' : '🟡' }} Server Alert\",\n \"description\": {{ JSON.stringify($json.summary) }},\n \"color\": {{ $json.severity === 'critical' ? 15158332 : $json.severity === 'high' ? 15105570 : 16776960 }},\n \"fields\": [\n {\n \"name\": \"Severity\",\n \"value\": \"{{ $json.severity.toUpperCase() }}\",\n \"inline\": true\n },\n {\n \"name\": \"Server\",\n \"value\": \"paper-dynasty (10.10.0.88)\",\n \"inline\": true\n },\n {\n \"name\": \"Cost\",\n \"value\": \"${{ $json.total_cost_usd ? $json.total_cost_usd.toFixed(4) : '0.0000' }}\",\n \"inline\": true\n },\n {\n \"name\": \"Root Cause\",\n \"value\": {{ JSON.stringify(($json.followup && $json.followup.root_cause_detail) || $json.root_cause || 'N/A') }},\n \"inline\": false\n },\n {\n \"name\": \"Affected Services\",\n \"value\": \"{{ $json.affected_services.length ? $json.affected_services.join(', ') : 'None' }}\",\n \"inline\": false\n },\n {\n \"name\": \"Remediation Steps\",\n \"value\": {{ JSON.stringify(($json.followup && $json.followup.remediation_steps.length) ? $json.followup.remediation_steps.map((s, i) => (i+1) + '. ' + s).join('\\n') : ($json.actions_taken.length ? $json.actions_taken.join('\\n') : 'None')) }},\n \"inline\": false\n },\n {\n \"name\": \"Requires Human?\",\n \"value\": \"{{ ($json.followup && $json.followup.requires_human) ? '⚠️ Yes' : '✅ No' }}\",\n \"inline\": true\n }\n ],\n \"timestamp\": \"{{ new Date().toISOString() }}\"\n }]\n}",
|
|
"options": {}
|
|
},
|
|
"id": "discord-alert",
|
|
"name": "Discord Alert",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [1320, -200]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://discord.com/api/webhooks/1451783909409816763/O9PMDiNt6ZIWRf8HKocIZ_E4vMGV_lEwq50aAiZ9HVFR2UGwO6J1N9_wOm82p0MetIqT",
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={\n \"embeds\": [{\n \"title\": \"Health Check OK\",\n \"description\": {{ JSON.stringify($json.result) }},\n \"color\": 3066993,\n \"fields\": [\n {\n \"name\": \"Server\",\n \"value\": \"paper-dynasty (10.10.0.88)\",\n \"inline\": true\n },\n {\n \"name\": \"Duration\",\n \"value\": \"{{ $json.duration_ms }}ms\",\n \"inline\": true\n }\n ],\n \"timestamp\": \"{{ new Date().toISOString() }}\"\n }]\n}",
|
|
"options": {}
|
|
},
|
|
"id": "discord-ok",
|
|
"name": "Discord OK (Optional)",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [880, 100],
|
|
"disabled": true
|
|
}
|
|
],
|
|
"connections": {
|
|
"Every 5 Minutes": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Run Claude Diagnostics",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Run Claude Diagnostics": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Parse Claude Response",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Parse Claude Response": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Has Issues?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Has Issues?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Follow Up Diagnostics",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Discord OK (Optional)",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Follow Up Diagnostics": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Parse Follow-up Response",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Parse Follow-up Response": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Discord Alert",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"settings": {
|
|
"executionOrder": "v1"
|
|
},
|
|
"staticData": null,
|
|
"tags": [],
|
|
"triggerCount": 0,
|
|
"pinData": {}
|
|
}
|