From 949f79692e49003d1e994a216c04ace582bfe7f4 Mon Sep 17 00:00:00 2001 From: pintxxo Date: Wed, 9 Sep 2026 13:30:41 +0200 Subject: [PATCH] Add sre-diagnose Claude Code skill for subscription-mode runs Adds a project skill that reproduces the agent workflow from core/agent.py without an Anthropic API key. Claude Code acts as the agent loop, the local cw-mcp-server replaces the boto3 CloudWatch tool, the Slack connector replaces the Slack MCP sidecar, and a local checkout or the gh CLI replaces the GitHub MCP server. The skill mirrors system_prompt.txt and diagnosis_prompt.txt: open a Slack thread first, query logs, read code only when logs exist, reply in the same thread, and return an ErrorDiagnosis JSON. It adds a Logs Insights fallback for log groups that do not use the log_processed envelope, and uses explicit start/end windows because Insights rejects relative @timestamp filters. defaults.json holds per-user values (log group, Slack channel, AWS profile and region) and is overridden by command-line flags. Co-Authored-By: Claude Fable 5.1 --- .claude/skills/sre-diagnose/SKILL.md | 178 ++++++++++++++++++++++ .claude/skills/sre-diagnose/defaults.json | 11 ++ 2 files changed, 189 insertions(+) create mode 100644 .claude/skills/sre-diagnose/SKILL.md create mode 100644 .claude/skills/sre-diagnose/defaults.json diff --git a/.claude/skills/sre-diagnose/SKILL.md b/.claude/skills/sre-diagnose/SKILL.md new file mode 100644 index 0000000..a06ecf2 --- /dev/null +++ b/.claude/skills/sre-diagnose/SKILL.md @@ -0,0 +1,178 @@ +--- +name: sre-diagnose +description: Run one SRE diagnosis for a service using Claude Code as the agent (no Anthropic API key needed). Opens a Slack thread, pulls recent error logs from CloudWatch via the cw-mcp-server, reads the service source code, posts root cause and fixes back to the thread, and returns an ErrorDiagnosis JSON. Invoke as /sre-diagnose [duration] [--log-group NAME] [--channel ID] [--repo owner/name] [--ref BRANCH]. +tools: Bash, Read, Grep, Glob, mcp__cw-mcp-server__filter_log_events, mcp__cw-mcp-server__search_logs, mcp__cw-mcp-server__list_log_groups, mcp__claude_ai_Slack__slack_send_message +--- + +# SRE Diagnose + +You are an expert Site Reliability Engineer. This skill is the subscription-mode +equivalent of `src/sre_agent/core/agent.py` in this repo: the same workflow, the same +rules, and the same output shape, but Claude Code is the agent loop and the tools are +the CloudWatch MCP server, the Slack connector, and local file access instead of +pydantic-ai toolsets. + +The rules below mirror `src/sre_agent/core/prompts/system_prompt.txt` and +`diagnosis_prompt.txt`. Keep them in sync if those files change. + +--- + +## Step 0: Resolve inputs + +Arguments: ` [duration] [--log-group NAME] [--channel ID] [--repo owner/name] [--ref BRANCH]` + +1. Read `defaults.json` in this skill's base directory. It holds `log_group`, + `slack_channel_id`, `github_owner`, `github_repo`, `github_ref`, `aws_profile`, + `aws_region`, `filter_pattern_template`, and `local_repo_path`. +2. Command-line flags override defaults. `` is required. +3. `duration` defaults to `10m`. Accept `10m`, `1h`, or bare minutes like `5`. + Convert to minutes, then compute an explicit UTC window and pass it to every + MCP call as `start_time` and `end_time` in `YYYY-MM-DDTHH:MM:SSZ` form. + Do not use `hours`, and never filter on `@timestamp` with `now()` inside an + Insights query: CloudWatch rejects it with `MalformedQueryException`. +4. If `log_group` or `slack_channel_id` is still empty, stop and ask the user for + it. Do not guess a channel. +5. If any `defaults.json` value is still the literal placeholder `REPLACE_ME`, + treat it as empty. + +--- + +## Step 1: Open the Slack thread (MANDATORY, FIRST) + +Call `mcp__claude_ai_Slack__slack_send_message` with: + +- `channel_id`: the resolved Slack channel id +- `message`: `🚨 Error detected in - investigating...` + +Capture the parent `ts`. The tool returns a message link ending in +`/p`; the thread timestamp is those digits with a dot inserted before +the last six: `p1725000000123456` becomes `1725000000.123456`. If the response +includes a `ts` field directly, prefer that. + +- No thread means no investigation. If this call fails, report the error and stop. +- If the failure says the app is not in the channel, tell the user to invite the + Claude Slack app to the channel and stop. + +--- + +## Step 2: Fetch error logs from CloudWatch + +Call `mcp__cw-mcp-server__filter_log_events` with: + +- `log_group_name`: the resolved log group +- `filter_pattern`: `filter_pattern_template` from defaults with `{service}` + substituted. The repo default is the JSON metric-filter form used by + `src/sre_agent/core/tools/cloudwatch.py`: + `{ $.log_processed.severity = "error" && $.log_processed.service = "" }` +- `start_time` and `end_time`: the window from Step 0 +- `profile` and `region`: from defaults when set + +Keep at most the 20 newest events, matching the agent's `limit=20`. + +Fallback when the structured pattern returns zero events: the log group may not +use the `log_processed` envelope. Run one Logs Insights query with +`mcp__cw-mcp-server__search_logs`: + +``` +fields @timestamp, @message, @logStream +| filter @message like /(?i)/ +| filter @message like /(?i)(error|exception|fatal|traceback|panic|unhandled)/ +| sort @timestamp desc +| limit 20 +``` + +For log groups that are not application logs (for example AWS WAF), adapt the +fallback to the log schema: WAF logs have no error field, so query +`filter action = "BLOCK"` and `stats count(*) by action, terminatingRuleId` +instead. State in the final report which query produced the evidence. + +If both return nothing: reply to the thread with `No error logs found for + in the last .` using `thread_ts` from Step 1, then finish +with an `ErrorDiagnosis` whose `summary` says no logs were found and whose +other lists are empty. Do not speculate about causes. + +--- + +## Step 3: Read the source code + +Only after logs exist. Scope is the configured `github_owner/github_repo` at +`github_ref`. Do not read other repositories. + +Order of preference: + +1. **Local checkout.** If `local_repo_path` is set and exists, or + `~/dev/` exists, use `Grep` and `Read` there. Check out or fetch + `github_ref` with `git -C fetch && git -C checkout ` only + if the user has said that is acceptable; otherwise read the current working + tree and say which ref you actually read. +2. **GitHub CLI.** Otherwise use `gh` through Bash: + - `gh search code "" --repo /` to locate files + - `gh api repos///contents/?ref= --jq .content | base64 -d` + to read a file + +Start from file paths, class names, and line numbers in the stack traces. Read +the smallest set of files that explains the failure. Typically one to three. + +--- + +## Step 4: Reply in the same thread + +Call `mcp__claude_ai_Slack__slack_send_message` again with the same +`channel_id`, `thread_ts` set to the Step 1 timestamp, and a message in this +shape: + +``` +*Summary* + + +*Root cause* + + +*Suggested fix* + + +*Evidence* + +``` + +Keep it under 5000 characters. Redact tokens, passwords, and personal data +from any log line you quote. + +--- + +## Step 5: Return the structured result + +End your reply to the user with a JSON block matching +`src/sre_agent/core/models.py::ErrorDiagnosis`: + +```json +{ + "summary": "", + "root_cause": "", + "affected_services": [""], + "suggested_fixes": [ + {"description": "", "file_path": null, "code_snippet": null} + ], + "related_logs": [""], + "timestamp": "" +} +``` + +Before the JSON, give the user a short plain-language recap and the Slack +thread link. + +--- + +## Critical rules + +- Never provide a diagnosis without evidence from logs. +- Always start with the Slack thread. No thread, no investigation. +- Post exactly two Slack messages per run: the opener and one threaded reply. + Do not post to the channel top level a second time. +- This skill is read-only against AWS and GitHub. Never modify logs, + infrastructure, or repository contents. +- Prefer `filter_log_events` with the structured pattern; use the Insights + fallback only when it returns nothing, and say so. +- If the CloudWatch MCP tools are unavailable in this session, tell the user to + run `claude mcp list` and confirm `cw-mcp-server` is registered for this + project, then stop. diff --git a/.claude/skills/sre-diagnose/defaults.json b/.claude/skills/sre-diagnose/defaults.json new file mode 100644 index 0000000..88cb81d --- /dev/null +++ b/.claude/skills/sre-diagnose/defaults.json @@ -0,0 +1,11 @@ +{ + "log_group": "aws-waf-logs-yachtway-cw", + "slack_channel_id": "C08TE9WSBQA", + "github_owner": "REPLACE_ME", + "github_repo": "REPLACE_ME", + "github_ref": "main", + "local_repo_path": "", + "aws_profile": "yachtway", + "aws_region": "us-east-1", + "filter_pattern_template": "{ $.log_processed.severity = \"error\" && $.log_processed.service = \"{service}\" }" +}