CI/CD Health Report
Produce an actionable weekly health report for Jenkins CI builds using Boost MCP tools. The report root-causes failures and groups them — a list of build numbers is not a report.
Configuration
See _shared/report-config.md for the standard config resolution pattern.
Config schema (~/.claude/skills/ci-health-report/config.json):
repos: list of {hostType, projectKey, repoSlug} objects
controllers: list of Jenkins controller names
job_name_contains: substring match on job name
lookback_days: number (default: 7)
Required scope: at least one of repos, controllers, or job_name_contains.
Arguments
- repos: list of {hostType, projectKey, repoSlug} to check
- controllers: Jenkins controller names to scope search
- job_name_contains: substring match on job name
- lookback_days: how far back to check builds (default: match your cadence — 1 for daily, 7 for weekly)
Workflow
Discover jobs. Use search_jobs with configured scope. Filter to ACTIVE state.
Get recent builds. For each job, call search_builds. Compute per-job: total builds, failures, success rate.
Resolve what each build was building. For each failed build, use the commitId to determine the PR or branch context. Use gh pr list --search <commitSha> or parse the triggerEventType + ref. The report must show linked PRs — not bare build numbers or commit SHAs. Construct the PR URL from the repo discovered in step 1: https://{github_host}/{projectKey}/{repoSlug}/pull/{number}. Format as Slack mrkdwn links: <https://{github_host}/{projectKey}/{repoSlug}/pull/1234|PR #1234>: title.
Fetch build logs and root-cause failures. For EVERY failed build:
- Get the build UUID from the
id field in search_builds results
- Read the log via
ReadMcpResourceTool(server="{ci_mcp_server}", uri="build-log://{build_uuid}")
- This returns a presigned S3 URL — download it with
curl -sL "{url}" | tail -50
- Extract the actual error: test failure name, compilation error, timeout, setup failure, etc.
- Classify each failure into a root cause category.
- Capture the Jenkins build URL from the build result (construct from controller + job name + build number:
https://{controller_host}/job/{job_name}/{build_number}/). Include this link in the report so readers can jump straight to the build console.
- NEVER use WebFetch on Jenkins URLs — they may require authentication. Always use
build-log://.
Group failures by root cause. Multiple builds may fail for the same reason (same test, same compilation error, same infra issue). Deduplicate. The report should say "TestWorkerRetry.test_timeout failed 5 times across 3 PRs" — not list 5 separate failures.
Identify flaky vs broken. A job is "flaky" if failure rate is 10-90% AND failures have multiple distinct root causes. A job is "broken" if failure rate >90% OR all failures share one root cause.
Generate report. Output markdown:
## CI/CD Health Report — {date}
Job: {job_name} | Controller: {controller} | Repo: {project}/{repo}
Period: last {lookback_days} days | Builds: {total} | Success rate: {pct}%
### Failure Root Causes (grouped)
#### 1. {root cause category} — {N} failures
Error: {actual error message or test name}
Affected PRs: <https://{github_host}/{project}/{repo}/pull/{number}|PR #{number}>: {title}, ...
Pattern: {infra flake / test bug / code bug / timeout}
Example log snippet: {2-3 key lines from the log}
Failed builds: <{jenkins_build_url}|Build #{number}>, ...
#### 2. {root cause category} — {N} failures
...
### Flaky Tests
(Tests that sometimes pass, sometimes fail — with pass/fail counts)
### Build Performance
- Median duration: {time} | P95: {time}
- Fastest: {time} | Slowest: {time}
### Healthy Builds
{N} builds succeeded across {N} PRs in the period.
Deliver as HTML. Follow the shared HTML delivery pattern in _shared/html-delivery.md. Report name: ci-health. TLDR includes job name, success rate, and top failure root cause.
Terminate. Report is complete when all failure root causes are grouped with log evidence.
Design Principles
- Team-level only. Aggregate to team level — it is the right granularity for a periodic digest. Individual-level detail (commits per person, PR count per author) is too noisy for a team report.
- Deterministic math first, LLM narrates only. All classification (severity, flaky vs broken, pass/fail) must be computed deterministically from data. The LLM writes prose around the numbers but never assigns severity or makes classification judgments.
- Pair metrics with counter-metrics. Never report velocity without stability. If showing deploy frequency, also show change failure rate. If showing PR throughput, also show rework rate.
Golden Rules
- Root-cause every failure. Fetch the build log. Extract the error. "Build failed" is not a root cause.
- Group by root cause, not by build. 5 builds failing the same test is ONE issue, not five.
- Show what was being built — with links. PR number + title as a clickable Slack mrkdwn link (
<url|PR #N>), or branch name. Never bare build numbers or commit SHAs — those mean nothing to the reader.
- Log evidence is mandatory. Every root cause group includes 2-3 key lines from the actual build log proving the classification.
- Flaky != broken. Multiple distinct root causes with mixed pass/fail = flaky. Single root cause dominating = broken.
- Check ALL builds in scope, not a sample.
Anti-Rationalization Counter-Table
| Excuse |
Reality |
| "I listed all the failures with their build numbers." |
Build numbers are meaningless. Show PR context and group by root cause. A list of numbers is not a report. |
| "I classified the failure from the status alone." |
FAILED status tells you nothing. The build log tells you what actually broke. Fetch it. |
| "Each failure is different so I listed them separately." |
Did you actually read the logs? Multiple builds often fail the same test or hit the same infra issue. Group them. |
| "I can't fetch the build log." |
The logUrl is in every build result. WebFetch it. If it's too large, fetch the tail. |
| "The PR context isn't available." |
The commitId is in every build. Use gh CLI to find the associated PR. |
Termination Labels
| Label |
Meaning |
report_complete |
All builds checked, failures root-caused from logs, grouped by cause, PR context resolved |
report_partial |
Some builds checked but log fetch or PR resolution failed for some — noted which |
no_jobs_found |
search_jobs returned empty — scope may be wrong |
api_error |
Boost API unreachable or returning errors |
Self-Review Checklist
1---2name: ci-health-report3description: Use when checking CI/CD health, Jenkins build failures, flaky tests, build success rates, or generating a CI health report. Trigger phrases: CI health, build failures, jenkins report, flaky tests, build health, are builds passing, CI status.4---56# CI/CD Health Report78Produce an actionable weekly health report for Jenkins CI builds using Boost MCP tools. The report root-causes failures and groups them — a list of build numbers is not a report.910## Configuration1112See [`_shared/report-config.md`](../_shared/report-config.md) for the standard config resolution pattern.1314**Config schema** (`~/.claude/skills/ci-health-report/config.json`):15- `repos`: list of `{hostType, projectKey, repoSlug}` objects16- `controllers`: list of Jenkins controller names17- `job_name_contains`: substring match on job name18- `lookback_days`: number (default: 7)1920**Required scope:** at least one of `repos`, `controllers`, or `job_name_contains`.2122## Arguments2324- **repos**: list of {hostType, projectKey, repoSlug} to check25- **controllers**: Jenkins controller names to scope search26- **job_name_contains**: substring match on job name27- **lookback_days**: how far back to check builds (default: match your cadence — 1 for daily, 7 for weekly)2829## Workflow30311. **Discover jobs.** Use `search_jobs` with configured scope. Filter to ACTIVE state.32332. **Get recent builds.** For each job, call `search_builds`. Compute per-job: total builds, failures, success rate.34353. **Resolve what each build was building.** For each failed build, use the commitId to determine the PR or branch context. Use `gh pr list --search <commitSha>` or parse the triggerEventType + ref. The report must show linked PRs — not bare build numbers or commit SHAs. Construct the PR URL from the repo discovered in step 1: `https://{github_host}/{projectKey}/{repoSlug}/pull/{number}`. Format as Slack mrkdwn links: `<https://{github_host}/{projectKey}/{repoSlug}/pull/1234|PR #1234>: title`.36374. **Fetch build logs and root-cause failures.** For EVERY failed build:38 - Get the build UUID from the `id` field in search_builds results39 - Read the log via `ReadMcpResourceTool(server="{ci_mcp_server}", uri="build-log://{build_uuid}")`40 - This returns a presigned S3 URL — download it with `curl -sL "{url}" | tail -50`41 - Extract the actual error: test failure name, compilation error, timeout, setup failure, etc.42 - Classify each failure into a root cause category.43 - Capture the Jenkins build URL from the build result (construct from controller + job name + build number: `https://{controller_host}/job/{job_name}/{build_number}/`). Include this link in the report so readers can jump straight to the build console.44 - NEVER use WebFetch on Jenkins URLs — they may require authentication. Always use `build-log://`.45465. **Group failures by root cause.** Multiple builds may fail for the same reason (same test, same compilation error, same infra issue). Deduplicate. The report should say "TestWorkerRetry.test_timeout failed 5 times across 3 PRs" — not list 5 separate failures.47486. **Identify flaky vs broken.** A job is "flaky" if failure rate is 10-90% AND failures have multiple distinct root causes. A job is "broken" if failure rate >90% OR all failures share one root cause.49507. **Generate report.** Output markdown:5152```53## CI/CD Health Report — {date}54Job: {job_name} | Controller: {controller} | Repo: {project}/{repo}55Period: last {lookback_days} days | Builds: {total} | Success rate: {pct}%5657### Failure Root Causes (grouped)5859#### 1. {root cause category} — {N} failures60Error: {actual error message or test name}61Affected PRs: <https://{github_host}/{project}/{repo}/pull/{number}|PR #{number}>: {title}, ...62Pattern: {infra flake / test bug / code bug / timeout}63Example log snippet: {2-3 key lines from the log}64Failed builds: <{jenkins_build_url}|Build #{number}>, ...6566#### 2. {root cause category} — {N} failures67...6869### Flaky Tests70(Tests that sometimes pass, sometimes fail — with pass/fail counts)7172### Build Performance73- Median duration: {time} | P95: {time}74- Fastest: {time} | Slowest: {time}7576### Healthy Builds77{N} builds succeeded across {N} PRs in the period.78```79808. **Deliver as HTML.** Follow the shared HTML delivery pattern in [`_shared/html-delivery.md`](../_shared/html-delivery.md). Report name: `ci-health`. TLDR includes job name, success rate, and top failure root cause.81829. **Terminate.** Report is complete when all failure root causes are grouped with log evidence.8384## Design Principles85861. **Team-level only.** Aggregate to team level — it is the right granularity for a periodic digest. Individual-level detail (commits per person, PR count per author) is too noisy for a team report.872. **Deterministic math first, LLM narrates only.** All classification (severity, flaky vs broken, pass/fail) must be computed deterministically from data. The LLM writes prose around the numbers but never assigns severity or makes classification judgments.883. **Pair metrics with counter-metrics.** Never report velocity without stability. If showing deploy frequency, also show change failure rate. If showing PR throughput, also show rework rate.8990## Golden Rules91921. **Root-cause every failure.** Fetch the build log. Extract the error. "Build failed" is not a root cause.932. **Group by root cause, not by build.** 5 builds failing the same test is ONE issue, not five.943. **Show what was being built — with links.** PR number + title as a clickable Slack mrkdwn link (`<url|PR #N>`), or branch name. Never bare build numbers or commit SHAs — those mean nothing to the reader.954. **Log evidence is mandatory.** Every root cause group includes 2-3 key lines from the actual build log proving the classification.965. **Flaky != broken.** Multiple distinct root causes with mixed pass/fail = flaky. Single root cause dominating = broken.976. **Check ALL builds in scope, not a sample.**9899## Anti-Rationalization Counter-Table100101| Excuse | Reality |102|---|---|103| "I listed all the failures with their build numbers." | Build numbers are meaningless. Show PR context and group by root cause. A list of numbers is not a report. |104| "I classified the failure from the status alone." | FAILED status tells you nothing. The build log tells you what actually broke. Fetch it. |105| "Each failure is different so I listed them separately." | Did you actually read the logs? Multiple builds often fail the same test or hit the same infra issue. Group them. |106| "I can't fetch the build log." | The logUrl is in every build result. WebFetch it. If it's too large, fetch the tail. |107| "The PR context isn't available." | The commitId is in every build. Use `gh` CLI to find the associated PR. |108109## Termination Labels110111| Label | Meaning |112|---|---|113| `report_complete` | All builds checked, failures root-caused from logs, grouped by cause, PR context resolved |114| `report_partial` | Some builds checked but log fetch or PR resolution failed for some — noted which |115| `no_jobs_found` | search_jobs returned empty — scope may be wrong |116| `api_error` | Boost API unreachable or returning errors |117118## Self-Review Checklist119120- [ ] Every failure has a root cause extracted from the actual build log121- [ ] Failures are grouped by root cause, not listed per-build122- [ ] Every failure group shows affected PRs as clickable links (Slack mrkdwn `<url|PR #N>`: title), not bare numbers or build IDs123- [ ] Log evidence (2-3 lines) included for each root cause group124- [ ] Each failed build includes a clickable Jenkins build link125- [ ] Failure rates computed from actual build counts126- [ ] Report includes build performance stats (median, p95 duration)127- [ ] HTML version uploaded to S3 with commuter link (unless `--no-html` or upload failed with noted fallback)128- [ ] Slack/chat delivery uses TLDR + link, not the full report