Purpose
Handle production incidents with speed, clarity, and accountability. This skill provides frameworks for on-call response, structured communication, rollback execution, and learning from failures.
Severity Classification
- SEV-1 (Critical): Complete service outage, data loss, or security breach. All hands on deck. Resolve in minutes.
- SEV-2 (Major): Significant degradation affecting most users. Dedicated incident commander. Target resolution under 1 hour.
- SEV-3 (Minor): Partial degradation, workaround available. On-call engineer handles. Target resolution under 4 hours.
- SEV-4 (Low): Cosmetic issues, minor bugs, non-user-facing failures. Handle during business hours.
- Classify based on user impact, not technical complexity. A simple bug affecting all users is higher severity than a complex bug affecting one.
- Escalate severity upward when impact grows. Never downgrade severity during an active incident.
On-Call Runbooks
BAD - Vague runbook:
## Database Issues
If the database is slow, check the connections and maybe restart it.
Contact the DBA if it doesn't work.
GOOD - Actionable runbook with copy-paste commands:
## Runbook: Database Connection Pool Exhausted
### Symptoms
- API returns 503 errors
- Grafana alert: `db_active_connections > 95% of pool_size`
- Logs: "connection pool exhausted, 0 idle connections"
### Diagnose (< 1 minute)
kubectl exec -it deploy/api -- curl localhost:8080/debug/db
# Shows: active=100, idle=0, max=100, waiting=47
psql -h $DB_HOST -c "SELECT count(*) FROM pg_stat_activity WHERE state='active';"
### Fix (choose one)
1. **Kill idle transactions** (safest):
psql -h $DB_HOST -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state='idle in transaction' AND query_start < now() - interval '5 min';"
2. **Increase pool size** (temporary):
kubectl set env deploy/api DB_POOL_SIZE=200
kubectl rollout restart deploy/api
3. **Rollback last deploy** (if caused by new code):
kubectl rollout undo deploy/api
### Do NOT
- Do NOT restart the database. Active transactions will be lost.
- Do NOT increase pool size above 200 without DBA approval.
### Escalation
- Slack: #team-platform | PagerDuty: Platform On-Call
Write runbooks for every known failure mode. Structure: symptom, diagnostic commands, fix steps, escalation contacts. Store where on-call engineers find them in 30 seconds. Link directly from monitoring alerts. Review quarterly.
Incident Command Structure
- Assign an Incident Commander (IC) immediately. The IC coordinates, does not debug.
- The IC's responsibilities: delegate tasks, track progress, communicate externally, decide on escalation.
- Assign a Communications Lead for SEV-1 and SEV-2 incidents. They update the status page and notify stakeholders.
- Engineers working the incident report findings to the IC, not to each other. Single channel of communication.
- Use a dedicated incident channel (Slack, Teams). Name it
#inc-YYYY-MM-DD-description.
- The IC calls the all-clear. No one else declares the incident resolved.
Communication Protocols
- First update within 5 minutes of detection for SEV-1, 15 minutes for SEV-2.
- Update every 30 minutes during an active incident, even if there is no progress. Silence causes panic.
- Use structured updates: current status, what we know, what we are doing, next update time.
- Internal updates go to the engineering team and leadership. External updates go to the status page.
- Avoid blame language during the incident. Focus on symptoms and actions.
- After resolution, send a final summary: root cause, fix applied, user impact duration, follow-up actions.
Status Page Management
- Maintain a public status page for external services. Update it before customers ask.
- Use clear, non-technical language: "Some users may experience slow page loads" over "Database replica lag exceeding threshold."
- Define status levels: Operational, Degraded Performance, Partial Outage, Major Outage.
- Pre-write templates for common scenarios. Fill in specifics during the incident.
- Include estimated resolution time when possible. Update it as the estimate changes.
- Post a follow-up after resolution confirming the issue is fully resolved.
Rollback Procedures
- Every deployment must have a documented rollback path before it ships.
- For application deployments: revert to the previous container image or artifact version.
- For database migrations: write and test the down migration before applying the up migration.
- For feature flags: disable the flag. This is the fastest rollback available.
- For infrastructure changes: use version-controlled IaC. Revert the commit and apply.
- Practice rollbacks in staging. A rollback you have never tested is a guess, not a plan.
- Set a time limit for debugging before rolling back. For SEV-1, roll back first, debug later.
- Preserve logs, metrics, and state before rolling back. You need evidence for the postmortem.
Blameless Postmortems
BAD - Blame-focused postmortem:
Root Cause: John deployed broken code on Friday at 5pm without testing.
Action Items: John needs to be more careful.
GOOD - Systems-focused postmortem:
## Postmortem: API Outage 2025-03-15
### Timeline (UTC)
- 16:42 - Deploy v2.3.1 to production (automated via merge to main)
- 16:44 - Error rate spikes from 0.1% to 34%
- 16:47 - PagerDuty alert fires, on-call acknowledges
- 16:52 - IC declared, #inc-2025-03-15-api-errors created
- 16:58 - Root cause identified: missing DB migration
- 17:01 - Rollback initiated (kubectl rollout undo)
- 17:03 - Error rate returns to 0.1%, all-clear declared
### Impact
- Duration: 21 minutes
- Users affected: ~12,000 (API returned 500 on /orders endpoint)
- Revenue impact: ~$3,200 in failed checkouts
### Root Cause
Deploy v2.3.1 added a query on `orders.status_v2` column. The migration
to add this column was not included in the deploy pipeline.
### Contributing Factors
- CI pipeline does not run pending migrations before integration tests
- No pre-deploy check that verifies schema compatibility
- Deploy happened automatically on merge, no manual gate
### Action Items
| Action | Owner | Due |
|--------|-------|-----|
| Add migration check to CI pipeline | @platform | 2025-03-22 |
| Add schema compatibility pre-deploy hook | @platform | 2025-03-29 |
| Document migration-first deploy process | @docs | 2025-03-22 |
Conduct postmortems for every SEV-1 and SEV-2 within 48 hours. Focus on system failures, not human errors. Ask "why did the system allow this?" Track action item completion. Review quarterly for recurring themes.
SLO Breach Handling
- Define SLOs (Service Level Objectives) for latency, availability, and error rate before incidents happen.
- Calculate error budgets: if SLO is 99.9% uptime, the error budget is 43.2 minutes per month.
- When the error budget is exhausted, freeze feature releases and focus on reliability work.
- Alert at 50% and 80% error budget consumption. Do not wait until the budget is gone.
- Tie SLO breaches to business impact. Leadership cares about customer trust, not percentiles.
- Review SLOs quarterly. Adjust targets based on customer expectations and operational capacity.
- Use SLO data to justify reliability investments. "We burned 90% of error budget last month" is a concrete argument.
Post-Incident Improvement
- Convert every postmortem action item into a tracked issue with a deadline and an owner.
- Prioritize automation that prevents recurrence over documentation that describes it.
- Add monitoring for the specific failure mode that caused the incident.
- Update runbooks with lessons learned from the incident.
- Share incident learnings in team retrospectives and engineering all-hands.
- Measure MTTR (Mean Time to Resolution) and MTTD (Mean Time to Detection) over time. Both should trend downward.
PagerDuty/OpsGenie Webhook Integration
// ✅ Production-Ready: PagerDuty Events API v2
import axios from "axios";
interface PagerDutyEvent {
routing_key: string;
event_action: "trigger" | "acknowledge" | "resolve";
dedup_key?: string;
payload: {
summary: string;
severity: "critical" | "error" | "warning" | "info";
source: string;
timestamp?: string;
custom_details?: Record<string, unknown>;
};
}
async function triggerPagerDutyAlert(
routingKey: string,
summary: string,
severity: "critical" | "error" | "warning" | "info",
details?: Record<string, unknown>
) {
const event: PagerDutyEvent = {
routing_key: routingKey,
event_action: "trigger",
payload: {
summary,
severity,
source: "monitoring-system",
timestamp: new Date().toISOString(),
custom_details: details,
},
};
const response = await axios.post(
"https://events.pagerduty.com/v2/enqueue",
event,
{ headers: { "Content-Type": "application/json" } }
);
return response.data.dedup_key; // Use this to acknowledge/resolve later
}
// Usage: trigger SEV-1 alert
const dedupKey = await triggerPagerDutyAlert(
process.env.PAGERDUTY_ROUTING_KEY!,
"Database connection pool exhausted",
"critical",
{ activeConnections: 100, maxConnections: 100, queuedRequests: 250 }
);
# OpsGenie Alert API
import requests
import os
def create_opsgenie_alert(message: str, priority: str, details: dict):
"""Create OpsGenie alert via REST API."""
url = "https://api.opsgenie.com/v2/alerts"
headers = {
"Authorization": f"GenieKey {os.getenv('OPSGENIE_API_KEY')}",
"Content-Type": "application/json"
}
payload = {
"message": message,
"priority": priority, # P1-P5
"details": details,
"tags": ["production", "automated"],
"source": "monitoring"
}
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
return response.json()["requestId"]
# Usage
create_opsgenie_alert(
message="API latency p99 > 2s for 5 minutes",
priority="P1",
details={"p99_latency": "2.3s", "endpoint": "/api/orders", "region": "us-east-1"}
)
1---2name: incident-response3description: Engineering incident response covering on-call runbooks, blameless postmortems, status pages, rollback procedures, communication protocols, severity levels, and SLO breach handling.4---56## Purpose78Handle production incidents with speed, clarity, and accountability. This skill provides frameworks for on-call response, structured communication, rollback execution, and learning from failures.910## Severity Classification1112- **SEV-1 (Critical):** Complete service outage, data loss, or security breach. All hands on deck. Resolve in minutes.13- **SEV-2 (Major):** Significant degradation affecting most users. Dedicated incident commander. Target resolution under 1 hour.14- **SEV-3 (Minor):** Partial degradation, workaround available. On-call engineer handles. Target resolution under 4 hours.15- **SEV-4 (Low):** Cosmetic issues, minor bugs, non-user-facing failures. Handle during business hours.16- Classify based on user impact, not technical complexity. A simple bug affecting all users is higher severity than a complex bug affecting one.17- Escalate severity upward when impact grows. Never downgrade severity during an active incident.1819## On-Call Runbooks2021**BAD** - Vague runbook:22```markdown23## Database Issues24If the database is slow, check the connections and maybe restart it.25Contact the DBA if it doesn't work.26```2728**GOOD** - Actionable runbook with copy-paste commands:29```markdown30## Runbook: Database Connection Pool Exhausted3132### Symptoms33- API returns 503 errors34- Grafana alert: `db_active_connections > 95% of pool_size`35- Logs: "connection pool exhausted, 0 idle connections"3637### Diagnose (< 1 minute)38 kubectl exec -it deploy/api -- curl localhost:8080/debug/db39 # Shows: active=100, idle=0, max=100, waiting=474041 psql -h $DB_HOST -c "SELECT count(*) FROM pg_stat_activity WHERE state='active';"4243### Fix (choose one)441. **Kill idle transactions** (safest):45 psql -h $DB_HOST -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state='idle in transaction' AND query_start < now() - interval '5 min';"46472. **Increase pool size** (temporary):48 kubectl set env deploy/api DB_POOL_SIZE=20049 kubectl rollout restart deploy/api50513. **Rollback last deploy** (if caused by new code):52 kubectl rollout undo deploy/api5354### Do NOT55- Do NOT restart the database. Active transactions will be lost.56- Do NOT increase pool size above 200 without DBA approval.5758### Escalation59- Slack: #team-platform | PagerDuty: Platform On-Call60```6162Write runbooks for every known failure mode. Structure: symptom, diagnostic commands, fix steps, escalation contacts. Store where on-call engineers find them in 30 seconds. Link directly from monitoring alerts. Review quarterly.6364## Incident Command Structure6566- Assign an Incident Commander (IC) immediately. The IC coordinates, does not debug.67- The IC's responsibilities: delegate tasks, track progress, communicate externally, decide on escalation.68- Assign a Communications Lead for SEV-1 and SEV-2 incidents. They update the status page and notify stakeholders.69- Engineers working the incident report findings to the IC, not to each other. Single channel of communication.70- Use a dedicated incident channel (Slack, Teams). Name it `#inc-YYYY-MM-DD-description`.71- The IC calls the all-clear. No one else declares the incident resolved.7273## Communication Protocols7475- First update within 5 minutes of detection for SEV-1, 15 minutes for SEV-2.76- Update every 30 minutes during an active incident, even if there is no progress. Silence causes panic.77- Use structured updates: current status, what we know, what we are doing, next update time.78- Internal updates go to the engineering team and leadership. External updates go to the status page.79- Avoid blame language during the incident. Focus on symptoms and actions.80- After resolution, send a final summary: root cause, fix applied, user impact duration, follow-up actions.8182## Status Page Management8384- Maintain a public status page for external services. Update it before customers ask.85- Use clear, non-technical language: "Some users may experience slow page loads" over "Database replica lag exceeding threshold."86- Define status levels: Operational, Degraded Performance, Partial Outage, Major Outage.87- Pre-write templates for common scenarios. Fill in specifics during the incident.88- Include estimated resolution time when possible. Update it as the estimate changes.89- Post a follow-up after resolution confirming the issue is fully resolved.9091## Rollback Procedures9293- Every deployment must have a documented rollback path before it ships.94- For application deployments: revert to the previous container image or artifact version.95- For database migrations: write and test the down migration before applying the up migration.96- For feature flags: disable the flag. This is the fastest rollback available.97- For infrastructure changes: use version-controlled IaC. Revert the commit and apply.98- Practice rollbacks in staging. A rollback you have never tested is a guess, not a plan.99- Set a time limit for debugging before rolling back. For SEV-1, roll back first, debug later.100- Preserve logs, metrics, and state before rolling back. You need evidence for the postmortem.101102## Blameless Postmortems103104**BAD** - Blame-focused postmortem:105```markdown106Root Cause: John deployed broken code on Friday at 5pm without testing.107Action Items: John needs to be more careful.108```109110**GOOD** - Systems-focused postmortem:111```markdown112## Postmortem: API Outage 2025-03-15113114### Timeline (UTC)115- 16:42 - Deploy v2.3.1 to production (automated via merge to main)116- 16:44 - Error rate spikes from 0.1% to 34%117- 16:47 - PagerDuty alert fires, on-call acknowledges118- 16:52 - IC declared, #inc-2025-03-15-api-errors created119- 16:58 - Root cause identified: missing DB migration120- 17:01 - Rollback initiated (kubectl rollout undo)121- 17:03 - Error rate returns to 0.1%, all-clear declared122123### Impact124- Duration: 21 minutes125- Users affected: ~12,000 (API returned 500 on /orders endpoint)126- Revenue impact: ~$3,200 in failed checkouts127128### Root Cause129Deploy v2.3.1 added a query on `orders.status_v2` column. The migration130to add this column was not included in the deploy pipeline.131132### Contributing Factors133- CI pipeline does not run pending migrations before integration tests134- No pre-deploy check that verifies schema compatibility135- Deploy happened automatically on merge, no manual gate136137### Action Items138| Action | Owner | Due |139|--------|-------|-----|140| Add migration check to CI pipeline | @platform | 2025-03-22 |141| Add schema compatibility pre-deploy hook | @platform | 2025-03-29 |142| Document migration-first deploy process | @docs | 2025-03-22 |143```144145Conduct postmortems for every SEV-1 and SEV-2 within 48 hours. Focus on system failures, not human errors. Ask "why did the system allow this?" Track action item completion. Review quarterly for recurring themes.146147## SLO Breach Handling148149- Define SLOs (Service Level Objectives) for latency, availability, and error rate before incidents happen.150- Calculate error budgets: if SLO is 99.9% uptime, the error budget is 43.2 minutes per month.151- When the error budget is exhausted, freeze feature releases and focus on reliability work.152- Alert at 50% and 80% error budget consumption. Do not wait until the budget is gone.153- Tie SLO breaches to business impact. Leadership cares about customer trust, not percentiles.154- Review SLOs quarterly. Adjust targets based on customer expectations and operational capacity.155- Use SLO data to justify reliability investments. "We burned 90% of error budget last month" is a concrete argument.156157## Post-Incident Improvement158159- Convert every postmortem action item into a tracked issue with a deadline and an owner.160- Prioritize automation that prevents recurrence over documentation that describes it.161- Add monitoring for the specific failure mode that caused the incident.162- Update runbooks with lessons learned from the incident.163- Share incident learnings in team retrospectives and engineering all-hands.164- Measure MTTR (Mean Time to Resolution) and MTTD (Mean Time to Detection) over time. Both should trend downward.165166## PagerDuty/OpsGenie Webhook Integration167168```typescript169// ✅ Production-Ready: PagerDuty Events API v2170import axios from "axios";171172interface PagerDutyEvent {173 routing_key: string;174 event_action: "trigger" | "acknowledge" | "resolve";175 dedup_key?: string;176 payload: {177 summary: string;178 severity: "critical" | "error" | "warning" | "info";179 source: string;180 timestamp?: string;181 custom_details?: Record<string, unknown>;182 };183}184185async function triggerPagerDutyAlert(186 routingKey: string,187 summary: string,188 severity: "critical" | "error" | "warning" | "info",189 details?: Record<string, unknown>190) {191 const event: PagerDutyEvent = {192 routing_key: routingKey,193 event_action: "trigger",194 payload: {195 summary,196 severity,197 source: "monitoring-system",198 timestamp: new Date().toISOString(),199 custom_details: details,200 },201 };202203 const response = await axios.post(204 "https://events.pagerduty.com/v2/enqueue",205 event,206 { headers: { "Content-Type": "application/json" } }207 );208209 return response.data.dedup_key; // Use this to acknowledge/resolve later210}211212// Usage: trigger SEV-1 alert213const dedupKey = await triggerPagerDutyAlert(214 process.env.PAGERDUTY_ROUTING_KEY!,215 "Database connection pool exhausted",216 "critical",217 { activeConnections: 100, maxConnections: 100, queuedRequests: 250 }218);219```220221```python222# OpsGenie Alert API223import requests224import os225226def create_opsgenie_alert(message: str, priority: str, details: dict):227 """Create OpsGenie alert via REST API."""228 url = "https://api.opsgenie.com/v2/alerts"229 headers = {230 "Authorization": f"GenieKey {os.getenv('OPSGENIE_API_KEY')}",231 "Content-Type": "application/json"232 }233234 payload = {235 "message": message,236 "priority": priority, # P1-P5237 "details": details,238 "tags": ["production", "automated"],239 "source": "monitoring"240 }241242 response = requests.post(url, json=payload, headers=headers)243 response.raise_for_status()244 return response.json()["requestId"]245246# Usage247create_opsgenie_alert(248 message="API latency p99 > 2s for 5 minutes",249 priority="P1",250 details={"p99_latency": "2.3s", "endpoint": "/api/orders", "region": "us-east-1"}251)252```