{"id":272,"date":"2026-11-07T09:41:00","date_gmt":"2026-11-07T17:41:00","guid":{"rendered":"https:\/\/blog.canutethegreat.com\/?p=272"},"modified":"2026-11-04T01:32:02","modified_gmt":"2026-11-04T09:32:02","slug":"incident-response-runbooks","status":"publish","type":"post","link":"https:\/\/blog.canutethegreat.com\/index.php\/2026\/11\/07\/incident-response-runbooks\/","title":{"rendered":"Designing an Incident Response Runbook That Actually Gets Used Under Pressure"},"content":{"rendered":"<p>Most incident response docs die the same death. Someone writes them once, during a calm retrospective, optimizing for completeness instead of usability. Six months later it&#8217;s 3 a.m., PagerDuty is screaming, there&#8217;s a VP in the Slack channel, and nobody opens the doc, because reading five paragraphs of prose to figure out what to do first is cognitively impossible under stress. I&#8217;ve watched competent engineers abandon a 40-page &#8220;IR plan&#8221; in under 90 seconds and just start guessing. The plan wasn&#8217;t wrong. It was unusable.<\/p>\n<p>A runbook that documents your incident process and a runbook that gets followed when your hands are shaking are two different documents.<\/p>\n<h2>Why prose-heavy runbooks fail<\/h2>\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/blog.canutethegreat.com\/wp-content\/uploads\/2026\/10\/incident-response-flow.png\" alt=\"Decision flow diagram for a credential-leak incident response runbook\" \/><\/figure>\n<p>Under acute stress, working memory collapses. You lose the ability to parse nested conditionals, skim for relevance, or hold context across paragraphs. That&#8217;s neurology, not a discipline problem. A document that reads like a design doc (&#8220;In the event that database replication lag exceeds acceptable thresholds, engineers should first consider&#8230;&#8221;) demands exactly the kind of sustained reading comprehension that incident stress destroys.<\/p>\n<p>I keep seeing the same three failure modes.<\/p>\n<p>No entry point. The doc assumes you already know which section applies. There&#8217;s no triage step that gets you from &#8220;something&#8217;s wrong&#8221; to &#8220;page 14&#8221; in under 10 seconds.<\/p>\n<p>Decisions buried in narrative. The actual decision (&#8220;page the DBA if X, roll back if Y&#8221;) sits in sentence four of paragraph three, surrounded by justification nobody needs mid-incident.<\/p>\n<p>Root-cause bias. The doc walks you toward understanding why before it tells you how to stop the bleeding. In a live incident, causality is a luxury. Containment is not.<\/p>\n<p>Better prose won&#8217;t fix this. Build runbooks as flowcharts and checklists first, and demote prose to footnotes and postmortem material.<\/p>\n<h2>What a usable structure looks like<\/h2>\n<p>Every runbook entry should be scannable top to bottom in the order a responder actually needs it, not the order that&#8217;s intellectually tidy.<\/p>\n<p>1. Trigger conditions: the literal alert names, dashboard thresholds, or symptom descriptions that mean &#8220;this runbook applies.&#8221; Not &#8220;if the database seems slow,&#8221; the actual PromQL alert name or Datadog monitor ID. Ambiguity here is the single biggest cause of wasted minutes.<\/p>\n<p>2. Severity tiers, defined by observable impact, not by vibes:<\/p>\n<pre tabindex=\"0\" class=\"wp-block-code\"><code>SEV1: customer-facing outage or data integrity risk, all hands, exec notified\nSEV2: degraded service, workaround exists, on-call + lead notified\nSEV3: internal-only or single-component failure, on-call handles solo<\/code><\/pre>\n<p>Tie the tier directly to escalation and comms obligations below. Don&#8217;t make the responder infer what SEV2 implies.<\/p>\n<p>3. First-5-minutes checklist: literal checkboxes, imperative verbs, zero interpretation required. This is the part people actually execute, so it has to survive being read by someone operating at 60% cognitive capacity.<\/p>\n<p>4. Escalation path: who, in what order, by what channel, with what SLA for response. Include a fallback if the primary doesn&#8217;t ack in N minutes. Name the PagerDuty schedule, the phone number, the backup. &#8220;Escalate&#8221; can&#8217;t be a vague gesture.<\/p>\n<p>5. Communication templates: pre-written, fill-in-the-blank status updates for internal Slack, status page, and customer-facing comms if applicable. Nobody should be composing prose for stakeholders while also fighting the fire.<\/p>\n<p>6. Containment and rollback steps, before root-cause investigation. This is the part teams get backwards most often. The runbook should assume the goal is &#8220;stop customer impact,&#8221; not &#8220;understand the bug.&#8221; Rollback, failover, feature-flag kill, traffic shedding, whatever gets you back to a known-good state comes before anyone opens a debugger or starts reading logs line by line. Root cause is a postmortem activity, not an incident-response one, unless root cause is the fix (killing a single bad host, say).<\/p>\n<p>Everything else, architecture diagrams, why the system is built this way, historical context, belongs in a linked reference doc, not the runbook itself. If it&#8217;s not actionable in the first 15 minutes, it doesn&#8217;t belong above the fold.<\/p>\n<h2>Concrete example: suspected credential leak<\/h2>\n<p>Take a credential or API key suspected to be exposed (committed to a public repo, leaked in a log, found in a pastebin).<\/p>\n<pre tabindex=\"0\" class=\"wp-block-code\"><code>RUNBOOK: Suspected Credential Leak\n\nTRIGGER CONDITIONS\n- GitHub secret-scanning alert fired\n- Credential found in public repo, log aggregator, or third-party report\n- Any employee reports pasting a secret into a public channel\/tool\n\nSEVERITY\nSEV1 if: production DB creds, cloud provider root\/admin keys, payment\n         processor keys\nSEV2 if: scoped service credentials, internal-only API keys\nSEV3 if: already-rotated or non-functional\/test credentials\n\nFIRST 5 MINUTES (do these in order, do not skip to investigation)\n[ ] 1. Identify exact credential(s) exposed (name, system, scope)\n[ ] 2. Revoke\/rotate the credential at the source NOW\n       - AWS: `aws iam update-access-key --access-key-id &lt;id&gt; --status Inactive`\n       - DB: rotate password, update secrets manager, do not wait for\n         app redeploy to confirm, deactivate first\n[ ] 3. Check access\/audit logs for USE of the credential since exposure\n       timestamp (not since discovery timestamp)\n[ ] 4. If evidence of use: treat as active compromise, escalate to SEV1,\n       page security on-call immediately\n[ ] 5. Post initial status in #incidents using template below\n\nESCALATION\n- Page: Security on-call (PagerDuty \"sec-oncall\"), ack SLA 5 min\n- If no ack in 5 min: page Security team lead directly (see contacts doc)\n- SEV1: notify Eng Director + CISO via phone, not Slack\n\nCOMMUNICATION TEMPLATE (#incidents channel)\n    INCIDENT: Credential exposure - [system\/service]\n    Severity: [SEV1\/2\/3]\n    Status: [Contained \/ Investigating \/ Monitoring]\n    Credential rotated: [Y\/N, timestamp]\n    Evidence of misuse: [Y\/N, details]\n    Next update: [time]\n    IC: [name]\n\nCONTAINMENT (before root-cause)\n[ ] Credential rotated\/revoked (confirmed, not assumed)\n[ ] Dependent services redeployed with new credential and confirmed healthy\n[ ] If public repo: force-push history rewrite or purge via GitHub support;\n    assume cached copies exist regardless (CDN\/scraper mirrors), rotation\n    is the real fix, not deletion\n[ ] If evidence of use: lock down affected resource (IP allowlist, disable\n    key entirely) even if it breaks a dependent system, customer data\n    risk outweighs availability here\n\nROOT CAUSE (postmortem, after containment confirmed)\n- How did the credential reach a public\/exposed location?\n- Was secret-scanning in place on this repo\/pipeline? If not, why not?\n- Time from exposure to detection, and detection to rotation<\/code><\/pre>\n<p>The excerpt leaves things out on purpose. It doesn&#8217;t explain OAuth, doesn&#8217;t lecture on secrets-management philosophy, doesn&#8217;t walk through how secret scanners work under the hood. All of that lives elsewhere. This section has one job: get a half-asleep on-call engineer from &#8220;alert fired&#8221; to &#8220;credential is dead and we know if it was misused&#8221; in under ten minutes, with zero ambiguity about who to call next.<\/p>\n<h2>Keeping it alive<\/h2>\n<p>A runbook this terse goes stale fast if nobody exercises it. I&#8217;ve seen teams write a beautiful one and then watch it rot for two years because it never got touched. Two practices help.<\/p>\n<p>Run it in tabletop exercises quarterly, with someone unfamiliar with the specific system reading it cold. If they get stuck or have to ask what a step means, that&#8217;s a bug in the doc, not the reader.<\/p>\n<p>Update it in the postmortem of every real incident it was used for. If a step was ambiguous or missing during the actual event, fix it that week. Runbooks nobody touches after a real use are already decaying, whether anyone notices or not.<\/p>\n<p>I&#8217;d take an unimpressive document that a tired, stressed, possibly junior engineer can execute correctly without me in the room. Write for that person, not for the auditor who&#8217;ll read it later.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most incident response docs die the same death. Someone writes them once, during a calm retrospective, optimizing for completeness instead of usability. Six months later it&#8217;s 3 a.m., PagerDuty is screaming, there&#8217;s a VP in the Slack channel, and nobody opens the doc, because reading five paragraphs of prose to figure out what to do [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":292,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21],"tags":[35,290,289],"class_list":["post-272","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security","tag-incident-response","tag-on-call","tag-runbooks"],"_links":{"self":[{"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/posts\/272","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/comments?post=272"}],"version-history":[{"count":2,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/posts\/272\/revisions"}],"predecessor-version":[{"id":538,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/posts\/272\/revisions\/538"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/media\/292"}],"wp:attachment":[{"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/media?parent=272"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/categories?post=272"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/tags?post=272"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}