Skip to content
Back to skills

Incident Management

ASecurity

Expert in incident response, on-call management, postmortems, and operational excellence. Use when automating CI/CD, deployments, or operations with incident management.

  • 9 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 8, 2026
businesspythongosqlexpressterraformapidatabasedevopsci/cdsecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add anubhavg-icpl/vibe --skill incident-management --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Incident Management?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Incident Management
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anubhavg-icpl-incident-management/badge)](https://www.skillsdirectory.com/skills/anubhavg-icpl-incident-management)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: incident-management
description: Expert in incident response, on-call management, postmortems, and operational excellence. Use when automating CI/CD, deployments, or operations with incident management.
license: CC-BY-NC-SA-4.0
metadata:
  risk: unknown
  source: community
  kind: mode
  category: devops
  tags: [incident-response, on-call, postmortem, pagerduty, opsgenie, runbooks, sre]
---

# Incident Management Expert Mode

You are an expert in incident management, covering incident response processes, on-call practices, alerting strategies, and blameless postmortems for operational excellence.

## Core Expertise

### Incident Lifecycle

- **Detection**: Alerting and monitoring
- **Triage**: Severity assessment and escalation
- **Response**: Coordination and mitigation
- **Resolution**: Root cause and fix
- **Postmortem**: Learning and prevention

### Key Tools

- **PagerDuty**: On-call and incident management
- **Opsgenie**: Alert and schedule management
- **Incident.io**: Slack-native incident response
- **Rootly**: Automated incident workflows
- **FireHydrant**: Incident management platform

## Code Standards

```python
# Incident Response Automation
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import List, Dict, Optional, Callable
from enum import Enum
import logging
import asyncio
import aiohttp
from abc import ABC, abstractmethod

logger = logging.getLogger(__name__)


class Severity(Enum):
    SEV1 = 1  # Critical - Complete outage
    SEV2 = 2  # Major - Significant impact
    SEV3 = 3  # Minor - Limited impact
    SEV4 = 4  # Low - Minimal impact


class IncidentStatus(Enum):
    TRIGGERED = "triggered"
    ACKNOWLEDGED = "acknowledged"
    INVESTIGATING = "investigating"
    IDENTIFIED = "identified"
    MONITORING = "monitoring"
    RESOLVED = "resolved"


@dataclass
class Incident:
    id: str
    title: str
    severity: Severity
    status: IncidentStatus
    description: str
    service: str
    created_at: datetime = field(default_factory=datetime.utcnow)
    acknowledged_at: Optional[datetime] = None
    resolved_at: Optional[datetime] = None
    commander: Optional[str] = None
    responders: List[str] = field(default_factory=list)
    timeline: List[Dict] = field(default_factory=list)
    affected_services: List[str] = field(default_factory=list)
    customer_impact: str = ""
    slack_channel: Optional[str] = None

    @property
    def duration(self) -> Optional[timedelta]:
        if self.resolved_at:
            return self.resolved_at - self.created_at
        return datetime.utcnow() - self.created_at

    @property
    def time_to_acknowledge(self) -> Optional[timedelta]:
        if self.acknowledged_at:
            return self.acknowledged_at - self.created_at
        return None

    def add_timeline_event(self, event: str, user: Optional[str] = None) -> None:
        self.timeline.append({
            "timestamp": datetime.utcnow().isoformat(),
            "event": event,
            "user": user,
        })


class AlertProvider(ABC):
    """Abstract base class for alert providers."""

    @abstractmethod
    async def create_incident(self, incident: Incident) -> str:
        pass

    @abstractmethod
    async def acknowledge(self, incident_id: str, user: str) -> None:
        pass

    @abstractmethod
    async def resolve(self, incident_id: str, resolution: str) -> None:
        pass

    @abstractmethod
    async def escalate(self, incident_id: str, policy: str) -> None:
        pass


class PagerDutyProvider(AlertProvider):
    """PagerDuty integration for incident management."""

    def __init__(self, api_key: str, service_id: str):
        self.api_key = api_key
        self.service_id = service_id
        self.base_url = "https://api.pagerduty.com"

    async def create_incident(self, incident: Incident) -> str:
        async with aiohttp.ClientSession() as session:
            payload = {
                "incident": {
                    "type": "incident",
                    "title": incident.title,
                    "service": {"id": self.service_id, "type": "service_reference"},
                    "urgency": "high" if incident.severity.value <= 2 else "low",
                    "body": {
                        "type": "incident_body",
                        "details": incident.description,
                    },
                }
            }

            async with session.post(
                f"{self.base_url}/incidents",
                json=payload,
                headers=self._headers(),
            ) as response:
                data = await response.json()
                return data["incident"]["id"]

    async def acknowledge(self, incident_id: str, user: str) -> None:
        async with aiohttp.ClientSession() as session:
            payload = {
                "incident": {
                    "type": "incident_reference",
                    "status": "acknowledged",
                }
            }
            await session.put(
                f"{self.base_url}/incidents/{incident_id}",
                json=payload,
                headers=self._headers(user),
            )

    async def resolve(self, incident_id: str, resolution: str) -> None:
        async with aiohttp.ClientSession() as session:
            payload = {
                "incident": {
                    "type": "incident_reference",
                    "status": "resolved",
                    "resolution": resolution,
                }
            }
            await session.put(
                f"{self.base_url}/incidents/{incident_id}",
                json=payload,
                headers=self._headers(),
            )

    async def escalate(self, incident_id: str, policy: str) -> None:
        async with aiohttp.ClientSession() as session:
            payload = {
                "incident": {
                    "type": "incident_reference",
                    "escalation_policy": {
                        "id": policy,
                        "type": "escalation_policy_reference",
                    },
                }
            }
            await session.put(
                f"{self.base_url}/incidents/{incident_id}",
                json=payload,
                headers=self._headers(),
            )

    def _headers(self, user_email: Optional[str] = None) -> Dict:
        headers = {
            "Authorization": f"Token token={self.api_key}",
            "Content-Type": "application/json",
        }
        if user_email:
            headers["From"] = user_email
        return headers


class IncidentManager:
    """Central incident management coordinator."""

    def __init__(
        self,
        alert_provider: AlertProvider,
        slack_client,
        runbook_service,
    ):
        self.alert_provider = alert_provider
        self.slack = slack_client
        self.runbooks = runbook_service
        self.active_incidents: Dict[str, Incident] = {}
        self.hooks: Dict[str, List[Callable]] = {
            "on_create": [],
            "on_acknowledge": [],
            "on_resolve": [],
            "on_escalate": [],
        }

    def register_hook(self, event: str, callback: Callable) -> None:
        """Register callback for incident events."""
        if event in self.hooks:
            self.hooks[event].append(callback)

    async def create_incident(
        self,
        title: str,
        severity: Severity,
        service: str,
        description: str,
        affected_services: Optional[List[str]] = None,
    ) -> Incident:
        """Create and manage a new incident."""
        incident = Incident(
            id="",  # Will be set by provider
            title=title,
            severity=severity,
            status=IncidentStatus.TRIGGERED,
            service=service,
            description=description,
            affected_services=affected_services or [service],
        )

        # Create in alert provider
        incident.id = await self.alert_provider.create_incident(incident)
        incident.add_timeline_event(f"Incident created: {title}")

        # Create Slack channel for SEV1/SEV2
        if severity.value <= 2:
            channel = await self._create_incident_channel(incident)
            incident.slack_channel = channel
            incident.add_timeline_event(f"Slack channel created: #{channel}")

        # Fetch relevant runbooks
        runbooks = await self.runbooks.find_runbooks(service, title)
        if runbooks:
            await self._post_runbooks(incident, runbooks)

        self.active_incidents[incident.id] = incident

        # Execute hooks
        for hook in self.hooks["on_create"]:
            await hook(incident)

        logger.info(f"Incident created: {incident.id} - {title}")
        return incident

    async def acknowledge(self, incident_id: str, user: str) -> None:
        """Acknowledge an incident."""
        incident = self.active_incidents.get(incident_id)
        if not incident:
            raise ValueError(f"Incident {incident_id} not found")

        await self.alert_provider.acknowledge(incident_id, user)

        incident.status = IncidentStatus.ACKNOWLEDGED
        incident.acknowledged_at = datetime.utcnow()
        incident.add_timeline_event(f"Acknowledged by {user}", user)

        if incident.slack_channel:
            await self.slack.post_message(
                incident.slack_channel,
                f"āœ… Incident acknowledged by {user}",
            )

        for hook in self.hooks["on_acknowledge"]:
            await hook(incident, user)

    async def update_status(
        self,
        incident_id: str,
        status: IncidentStatus,
        message: str,
        user: str,
    ) -> None:
        """Update incident status with message."""
        incident = self.active_incidents.get(incident_id)
        if not incident:
            raise ValueError(f"Incident {incident_id} not found")

        incident.status = status
        incident.add_timeline_event(f"Status → {status.value}: {message}", user)

        if incident.slack_channel:
            emoji = {
                IncidentStatus.INVESTIGATING: "šŸ”",
                IncidentStatus.IDENTIFIED: "šŸŽÆ",
                IncidentStatus.MONITORING: "šŸ‘€",
            }.get(status, "šŸ“‹")

            await self.slack.post_message(
                incident.slack_channel,
                f"{emoji} *{status.value.upper()}*: {message}",
            )

    async def resolve(
        self,
        incident_id: str,
        resolution: str,
        user: str,
    ) -> None:
        """Resolve an incident."""
        incident = self.active_incidents.get(incident_id)
        if not incident:
            raise ValueError(f"Incident {incident_id} not found")

        await self.alert_provider.resolve(incident_id, resolution)

        incident.status = IncidentStatus.RESOLVED
        incident.resolved_at = datetime.utcnow()
        incident.add_timeline_event(f"Resolved: {resolution}", user)

        if incident.slack_channel:
            await self.slack.post_message(
                incident.slack_channel,
                f"āœ… *RESOLVED*: {resolution}\n"
                f"Duration: {incident.duration}\n"
                f"Resolved by: {user}",
            )

        # Schedule postmortem for SEV1/SEV2
        if incident.severity.value <= 2:
            await self._schedule_postmortem(incident)

        for hook in self.hooks["on_resolve"]:
            await hook(incident, resolution)

        del self.active_incidents[incident_id]

    async def assign_commander(
        self,
        incident_id: str,
        commander: str,
    ) -> None:
        """Assign incident commander."""
        incident = self.active_incidents.get(incident_id)
        if not incident:
            raise ValueError(f"Incident {incident_id} not found")

        incident.commander = commander
        incident.add_timeline_event(f"Commander assigned: {commander}")

        if incident.slack_channel:
            await self.slack.post_message(
                incident.slack_channel,
                f"šŸ‘® *Incident Commander*: {commander}",
            )

    async def _create_incident_channel(self, incident: Incident) -> str:
        """Create dedicated Slack channel for incident."""
        timestamp = datetime.utcnow().strftime("%Y%m%d-%H%M")
        channel_name = f"inc-{incident.severity.name.lower()}-{timestamp}"

        channel = await self.slack.create_channel(channel_name)

        # Post incident summary
        await self.slack.post_message(
            channel,
            self._format_incident_summary(incident),
        )

        return channel

    def _format_incident_summary(self, incident: Incident) -> str:
        return f"""
🚨 *{incident.severity.name} Incident: {incident.title}*

*Service*: {incident.service}
*Impact*: {incident.customer_impact or 'Assessing...'}
*Affected Services*: {', '.join(incident.affected_services)}

*Description*:
{incident.description}

---
*Roles Needed*:
• Incident Commander: `/incident commander @user`
• Communications: `/incident comms @user`
• Technical Lead: `/incident tech @user`
"""

    async def _post_runbooks(self, incident: Incident, runbooks: List[Dict]) -> None:
        """Post relevant runbooks to incident channel."""
        if incident.slack_channel:
            message = "šŸ“š *Relevant Runbooks*:\n"
            for rb in runbooks:
                message += f"• <{rb['url']}|{rb['title']}>\n"

            await self.slack.post_message(incident.slack_channel, message)

    async def _schedule_postmortem(self, incident: Incident) -> None:
        """Schedule postmortem meeting and create document."""
        # Create postmortem document
        doc = await self._create_postmortem_doc(incident)

        if incident.slack_channel:
            await self.slack.post_message(
                incident.slack_channel,
                f"šŸ“ Postmortem document created: {doc['url']}\n"
                f"Meeting scheduled for 48 hours from now.",
            )


@dataclass
class OnCallSchedule:
    """On-call schedule management."""

    schedule_id: str
    name: str
    timezone: str
    rotation_type: str  # daily, weekly, custom
    users: List[str]
    escalation_timeout: int = 300  # seconds

    def get_current_oncall(self) -> str:
        """Get current on-call user."""
        # Simplified - real implementation would check time/rotation
        return self.users[0]


class EscalationPolicy:
    """Escalation policy for incidents."""

    def __init__(self, name: str, levels: List[Dict]):
        self.name = name
        self.levels = levels  # [{delay: 0, schedule: 'primary'}, ...]

    async def execute(
        self,
        incident: Incident,
        alert_provider: AlertProvider,
        current_level: int = 0,
    ) -> None:
        """Execute escalation policy."""
        if current_level >= len(self.levels):
            logger.warning(f"Escalation exhausted for incident {incident.id}")
            return

        level = self.levels[current_level]

        await asyncio.sleep(level["delay"])

        if incident.status == IncidentStatus.TRIGGERED:
            await alert_provider.escalate(incident.id, level["schedule"])
            incident.add_timeline_event(
                f"Escalated to level {current_level + 1}: {level['schedule']}"
            )

            # Continue escalation
            await self.execute(incident, alert_provider, current_level + 1)
```

```yaml
# PagerDuty Terraform Configuration
# pagerduty.tf
terraform {
  required_providers {
    pagerduty = {
      source  = "PagerDuty/pagerduty"
      version = "~> 3.0"
    }
  }
}

provider "pagerduty" {
  token = var.pagerduty_token
}

# Escalation Policy
resource "pagerduty_escalation_policy" "production" {
  name        = "Production Escalation"
  num_loops   = 2

  rule {
    escalation_delay_in_minutes = 5
    target {
      type = "schedule_reference"
      id   = pagerduty_schedule.primary.id
    }
  }

  rule {
    escalation_delay_in_minutes = 10
    target {
      type = "schedule_reference"
      id   = pagerduty_schedule.secondary.id
    }
  }

  rule {
    escalation_delay_in_minutes = 15
    target {
      type = "user_reference"
      id   = pagerduty_user.manager.id
    }
  }
}

# On-Call Schedules
resource "pagerduty_schedule" "primary" {
  name      = "Primary On-Call"
  time_zone = "America/New_York"

  layer {
    name                         = "Weekly Rotation"
    start                        = "2024-01-01T00:00:00-05:00"
    rotation_virtual_start       = "2024-01-01T00:00:00-05:00"
    rotation_turn_length_seconds = 604800  # 1 week

    users = [
      pagerduty_user.oncall1.id,
      pagerduty_user.oncall2.id,
      pagerduty_user.oncall3.id,
      pagerduty_user.oncall4.id,
    ]
  }
}

resource "pagerduty_schedule" "secondary" {
  name      = "Secondary On-Call"
  time_zone = "America/New_York"

  layer {
    name                         = "Weekly Rotation"
    start                        = "2024-01-01T00:00:00-05:00"
    rotation_virtual_start       = "2024-01-08T00:00:00-05:00"  # Offset by 1 week
    rotation_turn_length_seconds = 604800

    users = [
      pagerduty_user.oncall2.id,
      pagerduty_user.oncall3.id,
      pagerduty_user.oncall4.id,
      pagerduty_user.oncall1.id,
    ]
  }
}

# Service
resource "pagerduty_service" "api" {
  name                    = "API Service"
  escalation_policy       = pagerduty_escalation_policy.production.id
  alert_creation          = "create_alerts_and_incidents"
  auto_resolve_timeout    = 14400  # 4 hours
  acknowledgement_timeout = 1800   # 30 minutes

  incident_urgency_rule {
    type = "constant"
    urgency = "severity_based"
  }

  alert_grouping_parameters {
    type = "intelligent"
    config {
      time_window = 300
    }
  }
}

# Service Integration for Prometheus
resource "pagerduty_service_integration" "prometheus" {
  name    = "Prometheus"
  type    = "events_api_v2_inbound_integration"
  service = pagerduty_service.api.id
}

# Event Rules for Alert Routing
resource "pagerduty_event_orchestration" "router" {
  name = "Alert Router"

  set {
    id = "start"

    rule {
      label = "High CPU"
      condition {
        expression = "event.summary matches 'CPU'"
      }
      actions {
        severity = "warning"
        annotate = "High CPU detected"
      }
    }

    rule {
      label = "Error Rate"
      condition {
        expression = "event.summary matches 'error rate'"
      }
      actions {
        severity = "critical"
        annotate = "Error rate spike"
      }
    }
  }
}

# Maintenance Window
resource "pagerduty_maintenance_window" "deploy" {
  start_time  = "2024-01-15T02:00:00-05:00"
  end_time    = "2024-01-15T04:00:00-05:00"
  description = "Scheduled deployment window"

  services = [
    pagerduty_service.api.id,
  ]
}
```

```yaml
# Alertmanager Configuration with Routing
# alertmanager.yml
global:
  resolve_timeout: 5m
  slack_api_url: "https://hooks.slack.com/services/xxx"
  pagerduty_url: "https://events.pagerduty.com/v2/enqueue"

route:
  receiver: "default"
  group_by: ["alertname", "cluster", "service"]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h

  routes:
    # Critical alerts -> PagerDuty + Slack
    - match:
        severity: critical
      receiver: "pagerduty-critical"
      continue: true

    - match:
        severity: critical
      receiver: "slack-critical"

    # Warning alerts -> Slack only
    - match:
        severity: warning
      receiver: "slack-warnings"
      group_wait: 2m

    # Database alerts -> DBA team
    - match_re:
        service: (mysql|postgres|mongodb)
      receiver: "dba-team"

    # Security alerts -> Security team
    - match:
        team: security
      receiver: "security-team"

receivers:
  - name: "default"
    slack_configs:
      - channel: "#alerts-default"
        send_resolved: true

  - name: "pagerduty-critical"
    pagerduty_configs:
      - service_key: "<routing-key>"
        severity: critical
        description: "{{ .CommonAnnotations.summary }}"
        details:
          firing: '{{ template "pagerduty.firing" . }}'
          num_firing: "{{ .Alerts.Firing | len }}"
          num_resolved: "{{ .Alerts.Resolved | len }}"

  - name: "slack-critical"
    slack_configs:
      - channel: "#alerts-critical"
        color: "danger"
        title: "🚨 CRITICAL: {{ .CommonLabels.alertname }}"
        text: "{{ .CommonAnnotations.description }}"
        actions:
          - type: button
            text: "Runbook"
            url: "{{ .CommonAnnotations.runbook_url }}"
          - type: button
            text: "Silence"
            url: '{{ template "slack.silence.url" . }}'

  - name: "slack-warnings"
    slack_configs:
      - channel: "#alerts-warnings"
        color: "warning"
        send_resolved: true

  - name: "dba-team"
    pagerduty_configs:
      - service_key: "<dba-routing-key>"
    slack_configs:
      - channel: "#dba-alerts"

  - name: "security-team"
    pagerduty_configs:
      - service_key: "<security-routing-key>"
        severity: critical
    slack_configs:
      - channel: "#security-alerts"

inhibit_rules:
  # Inhibit warning if critical is firing
  - source_match:
      severity: "critical"
    target_match:
      severity: "warning"
    equal: ["alertname", "cluster", "service"]

  # Inhibit all if cluster is down
  - source_match:
      alertname: "ClusterDown"
    target_match_re:
      alertname: ".+"
    equal: ["cluster"]

templates:
  - "/etc/alertmanager/templates/*.tmpl"
```

```markdown
# Postmortem Template

## Incident Title

**Date**: YYYY-MM-DD
**Severity**: SEV1/SEV2/SEV3
**Duration**: X hours Y minutes
**Authors**: [Names]
**Status**: Draft/In Review/Complete

---

## Executive Summary

[2-3 sentence summary of what happened, impact, and resolution]

## Impact

- **Users Affected**: X users / Y% of traffic
- **Revenue Impact**: $X estimated
- **Duration**: Start time - End time (timezone)
- **Services Affected**: [List services]

## Timeline (All times in UTC)

| Time  | Event                                      |
| ----- | ------------------------------------------ |
| HH:MM | Alert triggered for [condition]            |
| HH:MM | On-call engineer [name] acknowledged       |
| HH:MM | Incident commander assigned: [name]        |
| HH:MM | Root cause identified: [brief description] |
| HH:MM | Fix deployed                               |
| HH:MM | Monitoring confirmed recovery              |
| HH:MM | Incident resolved                          |

## Root Cause

[Detailed technical explanation of what caused the incident]

## Resolution

[What was done to resolve the incident]

## Detection

- **How was it detected?**: [Monitoring/Customer report/etc.]
- **Time to detect**: X minutes
- **Could we have detected it sooner?**: [Yes/No - explanation]

## Response

- **Time to acknowledge**: X minutes
- **Time to mitigate**: X minutes
- **Time to resolve**: X minutes
- **What went well?**: [List items]
- **What could have gone better?**: [List items]

## Lessons Learned

### What went well

- [Item 1]
- [Item 2]

### What went poorly

- [Item 1]
- [Item 2]

### Where we got lucky

- [Item 1]

## Action Items

| Priority | Action                   | Owner | Due Date   | Status         |
| -------- | ------------------------ | ----- | ---------- | -------------- |
| P0       | [Immediate fix]          | @name | YYYY-MM-DD | āœ… Done        |
| P1       | [Short-term improvement] | @name | YYYY-MM-DD | šŸ”„ In Progress |
| P2       | [Long-term prevention]   | @name | YYYY-MM-DD | ā³ Pending     |

## Supporting Information

- **Monitoring Dashboards**: [Links]
- **Logs**: [Links to relevant log queries]
- **Related Incidents**: [Links to related postmortems]
- **Slack Channel**: #inc-sev1-YYYYMMDD

---

## Appendix

### Graphs and Charts

[Include relevant graphs showing the incident]

### Raw Timeline from Incident Channel

[Paste key messages from incident Slack channel]
```

```python
# Runbook Automation
from dataclasses import dataclass
from typing import List, Dict, Optional, Callable, Awaitable
from enum import Enum
import asyncio
import logging

logger = logging.getLogger(__name__)


class StepStatus(Enum):
    PENDING = "pending"
    RUNNING = "running"
    SUCCESS = "success"
    FAILED = "failed"
    SKIPPED = "skipped"


@dataclass
class RunbookStep:
    name: str
    description: str
    action: Callable[..., Awaitable[Dict]]
    requires_approval: bool = False
    rollback: Optional[Callable[..., Awaitable[None]]] = None
    timeout_seconds: int = 300
    retry_count: int = 0

    status: StepStatus = StepStatus.PENDING
    result: Optional[Dict] = None
    error: Optional[str] = None


class Runbook:
    """Executable runbook for incident response."""

    def __init__(
        self,
        name: str,
        description: str,
        steps: List[RunbookStep],
        approval_callback: Optional[Callable] = None,
    ):
        self.name = name
        self.description = description
        self.steps = steps
        self.approval_callback = approval_callback
        self.executed_steps: List[RunbookStep] = []

    async def execute(
        self,
        context: Dict,
        dry_run: bool = False,
    ) -> Dict:
        """Execute all runbook steps."""
        results = []

        for step in self.steps:
            logger.info(f"Executing step: {step.name}")
            step.status = StepStatus.RUNNING

            try:
                # Check for approval if required
                if step.requires_approval and not dry_run:
                    approved = await self._request_approval(step)
                    if not approved:
                        step.status = StepStatus.SKIPPED
                        continue

                if dry_run:
                    logger.info(f"[DRY RUN] Would execute: {step.name}")
                    step.status = StepStatus.SUCCESS
                    continue

                # Execute with timeout and retries
                result = await self._execute_with_retry(step, context)

                step.result = result
                step.status = StepStatus.SUCCESS
                self.executed_steps.append(step)
                results.append({"step": step.name, "result": result})

            except Exception as e:
                step.status = StepStatus.FAILED
                step.error = str(e)
                logger.error(f"Step {step.name} failed: {e}")

                # Attempt rollback of executed steps
                await self._rollback()

                return {
                    "success": False,
                    "failed_step": step.name,
                    "error": str(e),
                    "results": results,
                }

        return {"success": True, "results": results}

    async def _execute_with_retry(
        self,
        step: RunbookStep,
        context: Dict,
    ) -> Dict:
        """Execute step with timeout and retries."""
        last_error = None

        for attempt in range(step.retry_count + 1):
            try:
                result = await asyncio.wait_for(
                    step.action(context),
                    timeout=step.timeout_seconds,
                )
                return result
            except asyncio.TimeoutError:
                last_error = f"Step timed out after {step.timeout_seconds}s"
            except Exception as e:
                last_error = str(e)

            if attempt < step.retry_count:
                await asyncio.sleep(2 ** attempt)  # Exponential backoff

        raise Exception(last_error)

    async def _request_approval(self, step: RunbookStep) -> bool:
        """Request approval for step execution."""
        if self.approval_callback:
            return await self.approval_callback(step)
        return True

    async def _rollback(self) -> None:
        """Rollback executed steps in reverse order."""
        for step in reversed(self.executed_steps):
            if step.rollback:
                try:
                    logger.info(f"Rolling back: {step.name}")
                    await step.rollback()
                except Exception as e:
                    logger.error(f"Rollback failed for {step.name}: {e}")


# Example: Database Failover Runbook
async def check_primary_health(context: Dict) -> Dict:
    """Check primary database health."""
    # Implementation
    return {"healthy": False, "lag": 1000}


async def promote_replica(context: Dict) -> Dict:
    """Promote replica to primary."""
    # Implementation
    return {"new_primary": "db-replica-1"}


async def update_dns(context: Dict) -> Dict:
    """Update DNS to point to new primary."""
    # Implementation
    return {"dns_updated": True}


async def verify_connections(context: Dict) -> Dict:
    """Verify application connections."""
    # Implementation
    return {"connections": 150, "healthy": True}


db_failover_runbook = Runbook(
    name="Database Failover",
    description="Failover primary database to replica",
    steps=[
        RunbookStep(
            name="Check Primary Health",
            description="Verify primary database is unhealthy",
            action=check_primary_health,
        ),
        RunbookStep(
            name="Promote Replica",
            description="Promote replica to new primary",
            action=promote_replica,
            requires_approval=True,
            timeout_seconds=600,
        ),
        RunbookStep(
            name="Update DNS",
            description="Update DNS records",
            action=update_dns,
            retry_count=3,
        ),
        RunbookStep(
            name="Verify Connections",
            description="Verify application can connect",
            action=verify_connections,
        ),
    ],
)
```

## SLO-Based Alerting

```yaml
# Prometheus SLO Alerting Rules
groups:
  - name: slo-alerts
    rules:
      # Error budget burn rate alerts
      - alert: HighErrorBudgetBurn
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[1h]))
            /
            sum(rate(http_requests_total[1h]))
          ) > (1 - 0.999) * 14.4
        for: 5m
        labels:
          severity: critical
          slo: availability
        annotations:
          summary: "High error budget burn rate"
          description: "Error budget burning 14.4x faster than sustainable"
          runbook_url: "https://runbooks.example.com/high-error-rate"

      - alert: ErrorBudgetNearlyExhausted
        expr: |
          1 - (
            sum(increase(http_requests_total{status=~"5.."}[30d]))
            /
            sum(increase(http_requests_total[30d]))
          ) < 0.1
        for: 5m
        labels:
          severity: warning
          slo: availability
        annotations:
          summary: "Error budget nearly exhausted"
          description: "Less than 10% of monthly error budget remaining"

      # Latency SLO alerts
      - alert: LatencySLOBreach
        expr: |
          histogram_quantile(0.99,
            sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
          ) > 0.5
        for: 10m
        labels:
          severity: warning
          slo: latency
        annotations:
          summary: "P99 latency exceeds SLO"
          description: "P99 latency is {{ $value | humanizeDuration }} (SLO: 500ms)"
```

## Best Practices

### On-Call

- Rotate schedules to prevent burnout
- Provide clear escalation paths
- Document tribal knowledge in runbooks
- Review and improve alerts regularly

### Incident Response

- Assign clear roles (Commander, Comms, Tech Lead)
- Communicate early and often
- Focus on mitigation first, root cause later
- Document timeline in real-time

### Postmortems

- Conduct within 48-72 hours
- Focus on systems, not individuals
- Identify actionable improvements
- Track action items to completion

### Alerting

- Alert on symptoms, not causes
- Use SLO-based alerting
- Reduce noise to prevent fatigue
- Include runbook links in alerts

Incident management powers **Google, Uber, and Slack** maintaining 99.99% uptime.

You build reliable incident response systems with automation, clear processes, and continuous improvement.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.