SLOs and Error Budgets skill for Claude Code
Page when the budget burns, not when a graph wiggles. Claude defines SLIs as good over valid events measured near the user, picks targets from past performance, and uses a bundled calculator to derive the budget and the 14.4x, 6x and 1x burn-rate thresholds. It writes Prometheus recording and alerting rules, backtests them against past incidents, and drafts the policy that governs releases when the budget runs low.
- name
slo-error-budgets - size193 lines · ≈2,050 tokens
- files2
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Defines service level indicators and objectives from user journeys, computes error budgets, and builds multi-window multi-burn-rate alerts as Prometheus recording and alerting rules, plus an error budget policy that says what happens when the budget runs out. Use when choosing SLIs or SLO targets, replacing noisy threshold alerts with burn-rate alerts, writing Sloth, Pyrra, or OpenSLO specs, or answering "how reliable should this service be". Try asking
- Define SLOs for our checkout API and write the Prometheus alerts
- Our on-call gets paged for every error spike. Switch us to burn-rate alerting
- We had 9,000 failed requests this month on a 99.9% SLO. How much budget is left?
Use it when
- A service needs its first SLOs or the current ones are not trusted
- Threshold alerts are noisy or miss real incidents
- You need to decide how reliable a service should be
- Release decisions should depend on reliability data
Skip it when
- You are in the middle of an outage — use incident-response instead
- You need to emit the underlying metrics first — use opentelemetry instead
How to check the result
Backtesting the burn-rate alerts over the last 30 to 90 days fires for every customer-visible incident and stays quiet otherwise, and the bundled calculator matches the thresholds in the rules.
The common mistake it prevents
Setting the target from aspiration instead of history, so the service starts every window already out of budget and the alerts get ignored.
A good SLO measures what users experience at a boundary they touch, has a target the team can defend with data, turns its error budget into alerts that page only when the budget is genuinely at risk, and comes with a written policy for what happens when the budget is spent.
Ground rules
- SLIs are ratios: good events / valid events. Never “CPU < 80%” or “pod is up”.
- Measure as close to the user as practical: load balancer or API gateway beats the app’s own metrics; synthetic probes cover low-traffic paths.
- Pick targets from history, not aspiration. A target above what the service achieved last quarter just produces permanent alerts.
- Every dependency reduces achievable reliability; an SLO cannot exceed what critical dependencies provide without redundancy.
- Alert on burn rate, not on instantaneous error ratio.
- Fewer SLOs is better: 1–3 per user journey.
Workflow
- [ ] 1. List critical user journeys and where they are measured
- [ ] 2. Choose SLI type and exact good/valid event definitions
- [ ] 3. Pull 4+ weeks of history; set the target and window
- [ ] 4. Compute the budget and burn-rate thresholds (script)
- [ ] 5. Write recording rules + multi-window alerts
- [ ] 6. Write the error budget policy
- [ ] 7. Backtest alerts against past incidents; build the dashboard
2. SLI menu
| Journey type | SLI | Good event | Valid event |
|---|---|---|---|
| Request/response API | Availability | Response not 5xx (and not 429 caused by you) | All requests except health checks, bots |
| Request/response API | Latency | Served under threshold (e.g. 300 ms) | Same as availability |
| Data pipeline | Freshness | Data newer than N minutes when read/checked | Each check interval |
| Data pipeline | Correctness | Records passing validation | All processed records |
| Batch job | Completion | Job finished successfully before deadline | Scheduled runs |
| Async queue | End-to-end latency | Processed within N seconds of enqueue | Messages enqueued |
Decide explicitly whether 4xx count as bad (usually not; client errors) and whether timeouts at the client count (yes, if you can see them at the LB).
Latency SLIs use the fraction of requests below a threshold, not a percentile, because fractions add up across instances and windows; percentiles do not. The histogram must have a bucket boundary exactly at the threshold.
3. Target and window
- Window: rolling 28 or 30 days (28 keeps weekday mix constant).
- Use history: if last 4 weeks were 99.95%, a 99.9% target leaves room; 99.99% does not.
| Target | Budget per 30 days | Implication |
|---|---|---|
| 99% | 7.2 h | Internal tools, batch |
| 99.5% | 3.6 h | Many B2B features |
| 99.9% | 43.2 min | Typical customer-facing API |
| 99.95% | 21.6 min | Core revenue paths with redundancy |
| 99.99% | 4.3 min | Needs multi-region, automated failover, very fast rollback |
4. Budget and thresholds
python3 scripts/burn_rate.py --slo 99.9 # budget + alert thresholds
python3 scripts/burn_rate.py --slo 99.9 --error-ratio 0.004 # how bad is today?
python3 scripts/burn_rate.py --slo 99.9 --good 9991000 --total 10000000 --elapsed-days 12
Standard multi-window, multi-burn-rate policy (30-day window):
| Severity | Long window | Short window | Burn rate | Budget consumed when it fires |
|---|---|---|---|---|
| Page | 1 h | 5 min | 14.4 | 2% |
| Page | 6 h | 30 min | 6 | 5% |
| Ticket | 3 d | 6 h | 1 | 10% |
Burn rate = observed error ratio / (1 − SLO). The short window makes the alert reset quickly once the problem stops.
5. Prometheus rules
groups:
- name: slo-checkout-availability
rules:
- record: slo:sli_error:ratio_rate5m
expr: |
sum(rate(http_requests_total{job="checkout",code=~"5.."}[5m]))
/
sum(rate(http_requests_total{job="checkout"}[5m]))
labels: { slo: checkout-availability }
- record: slo:sli_error:ratio_rate30m
expr: |
sum(rate(http_requests_total{job="checkout",code=~"5.."}[30m]))
/ sum(rate(http_requests_total{job="checkout"}[30m]))
labels: { slo: checkout-availability }
- record: slo:sli_error:ratio_rate1h
expr: |
sum(rate(http_requests_total{job="checkout",code=~"5.."}[1h]))
/ sum(rate(http_requests_total{job="checkout"}[1h]))
labels: { slo: checkout-availability }
- record: slo:sli_error:ratio_rate6h
expr: |
sum(rate(http_requests_total{job="checkout",code=~"5.."}[6h]))
/ sum(rate(http_requests_total{job="checkout"}[6h]))
labels: { slo: checkout-availability }
- alert: CheckoutErrorBudgetBurnFast
expr: |
(slo:sli_error:ratio_rate1h{slo="checkout-availability"} > (14.4 * 0.001))
and
(slo:sli_error:ratio_rate5m{slo="checkout-availability"} > (14.4 * 0.001))
labels: { severity: page }
annotations:
summary: "Checkout burning 2% of monthly error budget per hour"
runbook_url: https://runbooks.example.com/checkout-availability
- alert: CheckoutErrorBudgetBurnSlow
expr: |
(slo:sli_error:ratio_rate6h{slo="checkout-availability"} > (6 * 0.001))
and
(slo:sli_error:ratio_rate30m{slo="checkout-availability"} > (6 * 0.001))
labels: { severity: page }
Add 2h/1d/3d rules the same way for the ticket alert (or compute long windows from
the 5m rule with avg_over_time if query cost matters). Latency SLI error ratio:
1 - (
sum(rate(http_request_duration_seconds_bucket{job="checkout",le="0.3"}[5m]))
/
sum(rate(http_request_duration_seconds_count{job="checkout"}[5m]))
)
Generators remove this boilerplate: Sloth and Pyrra take a short SLO spec and emit all recording rules and alerts. OpenSLO is a vendor-neutral spec format. Prefer a generator when there are more than two SLOs.
Low traffic (under ~1 request/min): burn-rate alerts flap on single errors. Add a minimum-event-count condition, use longer windows, or rely on synthetic probes.
6. Error budget policy
## Error budget policy — checkout (SLO 99.9% availability, 30d rolling)
- Budget remaining > 50%: normal feature velocity.
- 25–50%: each release requires a tested rollback; risky migrations need sign-off.
- < 25%: feature releases need approval from the service owner; reliability work first.
- Exhausted: freeze non-critical releases until the 30-day SLI recovers above target;
incident action items take priority over roadmap work.
- A single incident consuming > 20% of budget requires a postmortem.
- Exceptions: security fixes and changes that improve reliability are always allowed.
Owner: checkout team lead. Reviewed quarterly.
7. Backtest and dashboard
- Replay the alert expressions over the last 30–90 days of data (Grafana explore or promtool with recorded data). Every past customer-visible incident should have fired a page; quiet periods should not.
- Dashboard per SLO: SLI over the window vs. target, budget remaining (%), burn rate 1h/6h, and the events that consumed budget.
Gotchas
- Measuring at the app misses failures before the app (LB, DNS, TLS, crashed pods).
- Including health checks and bots in valid events inflates the SLI.
rate()over a window with no traffic returns no data; alerts silently never fire. Decide how to treat no-data (oftenabsent()alerts on the SLI itself).- Changing the SLI definition resets comparability; annotate the dashboard.
- 100% is never the target; it forbids change and has no budget to spend.
- An SLO nobody consults when planning is decoration. Tie it to the release policy.
Output
## SLO: checkout availability
- SLI: non-5xx responses / all requests at the ALB, excluding /healthz
- Target: 99.9% over rolling 30 days (last 90 days achieved: 99.96%)
- Budget: 43.2 min-equivalent / 0.1% of requests
- Alerts: page at 14.4x (1h & 5m) and 6x (6h & 30m); ticket at 1x (3d & 6h)
- Backtest: fired for both incidents in the last 90 days, 0 false pages
- Policy: linked; owner checkout team lead Bundled files
Loaded only when a step in the skill needs them. Review them here before installing.
scripts/burn_rate.py
#!/usr/bin/env python3
"""Error budget and burn-rate calculator (stdlib only).
Examples:
python3 scripts/burn_rate.py --slo 99.9
python3 scripts/burn_rate.py --slo 99.9 --window-days 28 --error-ratio 0.004
python3 scripts/burn_rate.py --slo 99.5 --good 9951234 --total 10000000
Prints the error budget, the standard multi-window multi-burn-rate alert thresholds
(Google SRE Workbook), and, if given, the current burn rate and budget remaining.
Exit code 1 when the budget is exhausted or the current burn rate would exhaust it
before the window ends; 0 otherwise; 2 on bad input.
"""
import argparse
import sys
# (budget fraction consumed, long window hours, short window hours, action)
POLICY = [
(0.02, 1, 5 / 60, "page"),
(0.05, 6, 0.5, "page"),
(0.10, 72, 6, "ticket"),
]
def fmt_duration(minutes):
if minutes < 120:
return f"{minutes:.1f} min"
hours = minutes / 60
if hours < 72:
return f"{hours:.1f} h"
return f"{hours / 24:.1f} days"
def fmt_window(hours):
if hours < 1:
return f"{round(hours * 60)}m"
if hours < 24 or hours % 24:
return f"{hours:g}h"
return f"{hours / 24:g}d"
def main():
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument("--slo", type=float, required=True, help="SLO target in percent, e.g. 99.9")
p.add_argument("--window-days", type=float, default=30, help="SLO window in days (default 30)")
p.add_argument("--error-ratio", type=float, help="current error ratio over a recent period (0-1)")
p.add_argument("--good", type=float, help="good events so far in the window")
p.add_argument("--total", type=float, help="valid events so far in the window")
p.add_argument("--elapsed-days", type=float, help="days elapsed in the window (with --good/--total)")
a = p.parse_args()
if not 0 < a.slo < 100:
print("error: --slo must be between 0 and 100 (exclusive)", file=sys.stderr)
return 2
budget = 1 - a.slo / 100
window_h = a.window_days * 24
window_min = window_h * 60
print(f"SLO {a.slo}% over {a.window_days:g} days")
print(f" error budget: {budget:.4%} of events")
print(f" as downtime-equivalent: {fmt_duration(budget * window_min)} per window")
print()
print("Multi-window burn-rate alerts (alert when BOTH windows exceed the threshold):")
print(f" {'action':7} {'long':>5} {'short':>6} {'burn':>6} {'error ratio >':>14} budget used at trigger")
for frac, long_h, short_h, action in POLICY:
burn = frac * window_h / long_h
print(
f" {action:7} {fmt_window(long_h):>5} {fmt_window(short_h):>6} {burn:>6.1f} "
f"{burn * budget:>14.5f} {frac:.0%}"
)
print()
print("PromQL shape (replace the recording rule names with yours):")
frac, long_h, short_h, _ = POLICY[0]
burn = frac * window_h / long_h
print(
f" (slo:sli_error:ratio_rate{fmt_window(long_h)} > ({burn:.1f} * {budget:.6f}))\n"
f" and\n"
f" (slo:sli_error:ratio_rate{fmt_window(short_h)} > ({burn:.1f} * {budget:.6f}))"
)
status = 0
if a.error_ratio is not None:
if not 0 <= a.error_ratio <= 1:
print("error: --error-ratio must be within 0..1", file=sys.stderr)
return 2
rate = a.error_ratio / budget if budget else float("inf")
print()
print(f"Current error ratio {a.error_ratio:.5f} -> burn rate {rate:.2f}x")
if rate > 0:
exhaust_h = window_h / rate
print(f" a full budget would be exhausted in {fmt_duration(exhaust_h * 60)} at this rate")
if rate > 1:
status = 1
print(" WARNING: burning faster than the budget allows")
if a.good is not None or a.total is not None:
if a.good is None or a.total is None or a.total <= 0 or a.good > a.total:
print("error: --good and --total must both be set with 0 <= good <= total, total > 0", file=sys.stderr)
return 2
bad = a.total - a.good
allowed = budget * a.total
remaining = 1 - (bad / allowed) if allowed else float("-inf")
print()
print(f"Window so far: {a.good / a.total:.5%} good ({bad:,.0f} bad of {a.total:,.0f})")
print(f" budget remaining: {remaining:.1%}")
if a.elapsed_days:
expected_used = a.elapsed_days / a.window_days
print(f" elapsed {a.elapsed_days:g}/{a.window_days:g} days; on-pace budget use would be {expected_used:.0%}")
if remaining <= 0:
status = 1
print(" BUDGET EXHAUSTED: apply the error budget policy")
return status
if __name__ == "__main__":
sys.exit(main())
About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.