mcpField
W
write-a-postmortem

write-a-postmortem

github.com/inkeep云与 DevOps14223 次安装1 天前更新
代码仓库

关于 write-a-postmortem

write-a-postmortem 是用于编写基于证据、避免归责的事故复盘 Agent 技能。它指导团队在 OpenKnowledge Markdown 工作区中整理时间线、检索历史事故、分析根因并制定后续行动。

主要功能

  1. 01避免归责的事故分析
  2. 02带证据来源的时间线
  3. 03检索相关历史事故
  4. 04区分触发因素、症状与根因
  5. 05行动项与运行手册草案

适用场景

  1. 01依据监控和部署记录记录故障
  2. 02判断事故是否重现已知故障类型
  3. 03将促成因素转化为可验证的后续行动

MCP 相关

该技能通过 OpenKnowledge MCP 读取和搜索范围内的 Markdown、创建或编辑复盘文档并检查文档链接。源代码、日志、仪表板和聊天记录则通过宿主原生工具读取。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/inkeep/open-knowledge-skills --skill write-a-postmortem

安装后在 Claude Code 中输入/write-a-postmortem确认已加载。

详细介绍

撰写事后复盘

平台 /open-knowledge skill 仍然规定这里的所有 Markdown 操作,包括文件夹范围、读写工具范围、链接和预览。此 skill 在此基础上加入事后复盘的撰写规范;它不会取代这些规则。

事后复盘不是状态更新,也不是追责清单。它是基于证据,对单次事故进行持久、无责备的还原,让系统的失效模式清晰可见,并产出有人会实际验证的行动项。只有当具有相同子系统的事后复盘彼此链接时,价值才会不断累积——这正是让重复出现的问题不再被忽视的方式。

此套件会在项目根目录下创建 proposals/、decisions/、specs/、postmortems/ 和 guides/。事后复盘存放在 postmortems/,每起事故一个文件,文件名为 YYYY-MM-DD-name.md,模板 id 为 postmortem。适合整理为指南的行动项会在 guides/ 中生成 runbook 占位文件。

知识库是由 OpenKnowledge MCP 管理的 Markdown 文档。使用 exec(exec("ls -A postmortems/")、exec("cat postmortems/2024-03-02-auth-outage.md")、exec("grep -rln failover ."))和 search({ query: "..." }) 读取和列出范围内的 Markdown;使用 write 和 edit 创建和编辑;使用 links 检查链接图。绝不能对范围内的 Markdown 使用原生 Read/Edit/Grep/Glob/cat。源代码、日志、仪表板、部署历史和聊天记录不属于知识库——请使用宿主提供的原生工具读取。链接使用普通 Markdown 相对链接 [db failover](./guides/db-failover.md)——绝不使用反引号,也不使用 HTML 锚点。


步骤 0 — 无责备检查关卡(先说明,因为它约束后续每个步骤)

无责备是一项必须严格遵守的规范,而不是贴在文档标题处的一句口号。如果无法严格遵守这些规则,那么无论文档开头怎么说,它都不是无责备的。

  • 写系统和角色,绝不写个人。 写“部署流水线”“值班工程师”“发布流程”——不要写“Alice”,也不要写“新员工”。点名某个人会让读者产生防御心理,分析也会就此止步。
  • 描述系统提供的条件,而不是个人的行为。 写“部署流水线允许未经审核的配置进入生产环境”——不要写“Alice 部署了未经审核的配置”。句子的主语应是导致该结果成为可能的系统。如果句子的主语是某个人,就改写,直到主语变成系统。
  • 将每个人的行为都视为当时信息条件下合理的选择。 没有人因为粗心而导致事故。有人根据当时掌握的信号做出了合理的选择。分析要问的绝不是“他们为什么那么做?”,而是“是什么让当时看来那是正确的做法?”仪表板显示绿色、告警从未触发、运行手册明确要求这么做。找出让这种选择显得合理的因素;那就是促成因素。

严格关卡。 如果用户的表达意在追责——“写清楚 Alice 是怎么搞坏生产环境的”“记录是谁把部署搞砸了”——不要照做。请明确说明,并将问题重新聚焦于系统,然后再开始撰写。改写示例:“我会以无责备的方式撰写——有价值的问题不是谁推送了配置,而是什么让未经审核的配置进入了生产环境。这才是能防止事故重演的发现。”仅仅因为用户要求,就撰写一份点名责任人的文档,是此 skill 可能犯下的最严重错误;它会毒害这份文档本应建立的事后复盘文化。

第 1 步 — 先收集证据,再叙述

不能凭记忆编写时间线,然后称之为事后复盘。先收集,再叙述。

使用主机的原生工具(这些工具位于知识库之外)提取:

  • 告警和监控 — 哪些告警触发了、何时触发,以及哪些本应触发却没有触发。
  • 部署和发布历史 — 事发前几小时发布了什么,以及确切的提交/配置。
  • 仪表板和指标 — 错误率、延迟、饱和度,以及显示故障开始和恢复情况的图表。
  • 聊天记录和事故频道 — 人员决策的时间戳,以及当时的决策依据。
  • 事发时的代码和配置 — 而非当前状态;检出或读取事故 SHA 对应的状态。

硬性要求 — 时间线中的每一项都必须有可指明的来源。 每一行时间线都要注明证据:告警 ID、部署时间戳、图表或聊天消息时间。如果你根据某人的回忆还原了某个时刻,却没有任何记录,仍可将其写入,但要在行内标注:(根据回忆还原,无记录)。时间线悄然混杂记录事实和记忆的事后复盘,比坦承信息缺失的复盘更糟,因为读者无法判断哪些数字可信。


第 2 步 — 查阅同一子系统的过往事后复盘

动笔之前,先确认这类问题是否已经发生过。

  1. exec("ls -A postmortems/") — 一览所有过往事故。
  2. search({ query: "<subsystem> <failure mode>" }) — 对受影响组件进行语义匹配(例如“数据库故障转移复制延迟”)。
  3. exec("grep -rln <subsystem-keyword> postmortems/") — 按服务、错误和机制精确搜索相关术语。
  4. 对最接近的 1–3 个结果执行 exec("cat postmortems/<candidate>.md") — 阅读其中的根因和行动项。

如果这是已记录过的同类问题再次发生,那么这就是整份文档中最重要的发现,应该写在摘要里,而不是埋在“相关信息”中。 问题再次发生意味着先前的行动项未完成,或虽已完成却未能防止复发。请在前两句话中说明:“这是支付服务第三次发生连接池耗尽事故(见 2024-01-11、2024-03-02);第二次事故的行动项始终未完成。”事后复盘的价值,尤其体现在揭示同类问题的反复发生——要醒目地指出这一点。


第 3 步 — 使用模板创建事后复盘

write({ document: { path: "postmortems/YYYY-MM-DD-name.md", template: "postmortem" } })
  • 日期应为事故发生日期,而不是今天。 如果故障发生于 2024-03-02,即使你一周后才撰写,文件名也应为 postmortems/2024-03-02-name.md。
  • name 是受影响系统和故障的简短 kebab 格式名称:payments-pool-exhaustion、auth-cert-expiry、search-index-corruption。
  • postmortem 模板提供 SRE 结构框架:摘要 / 时间线 / 根因 / 做得好的方面 / 行动项。请按以下步骤填写各部分;不要另行设计结构。

第 4 步 — 摘要:先写影响,再写原因

读摘要的人可能永远不会读其余部分。面向整个组织来写,而不是只面向当班工程师。

  • 先说明用户可见的影响: 哪些人受到影响、受到何种影响、持续了多久。“在 47 分钟内,约 12% 的结账请求因 500 错误而失败。”不要写“连接池达到饱和。”
  • 然后用一句话说明原因, 简明扼要:“配置更改将连接池大小减半,而当时流量正处于峰值;连接池耗尽,请求排队时间超过超时阈值。”
  • 如果第 2 步发现同类问题再次发生,应在此说明(见第 2 步)。

以技术问题开头(“连接池在 14:03 达到饱和”)并把影响埋在后面的摘要,是写给工程师看的,而不是写给需要评估事故的组织看的。影响是最重要的信息;请先用通俗的语言说明影响。

第 5 步 — 时间线:带时间戳、有来源,并明确检测延迟

按时间顺序重建事件。每条记录都应包含:带时区的时间戳、发生了什么,以及证据来源。

- 13:52 UTC — Config change #4821 merged, halving `pool.max` from 200 to 100. (deploy log, PR #4821)
- 14:03 UTC — Checkout error rate crosses 5%. (Grafana, checkout-errors panel)
- 14:19 UTC — First page fires to on-call. (PagerDuty incident #9917)
- 14:26 UTC — On-call acknowledges, begins investigating a suspected upstream issue. (incident channel)
- 14:41 UTC — Config change identified as cause; rollback initiated. (incident channel)
- 14:50 UTC — Error rate returns to baseline. (Grafana)
  • 明确指出检测时间和缓解时间。 标出事件开始、被检测到和被缓解的记录。
  • 事件开始到检测之间的间隔,通常是文档中最值得采取行动的数字。 本例中:14:03 开始,14:19 首次告警——盲区长达 16 分钟。紧接时间线后,用文字说明这一间隔:“从开始到检测滞后了 16 分钟;结账错误率没有触发告警,只有主机 CPU 触发了告警。” 这句话往往是最能带来成效的改进行动项的来源。
  • 保持记录客观,并注明来源。分析应放在根因部分,而不是这里。

第 6 步 — 触发因素与根因、症状

先向读者说明这三个定义,再清晰地区分它们。混淆这些概念是复盘无法提出有效改进措施的最常见原因。

  • 症状 — 观察到的现象。“结账请求返回 500 错误;连接池显示没有可用连接。” 症状是真实存在的,但它不是原因;修复症状(重启、扩容)可以结束这次事件,却无法防止下次发生。
  • 触发因素 — 引发事件的具体因素。“配置变更 #4821 在流量高峰期将连接池大小减半。” 触发因素是事件经过中不可或缺的一环,但很少是全部原因。
  • 根因 — 使触发因素足以导致服务中断的条件。不要停留在“推送了错误配置”上——要追问,是什么导致错误配置能够在高峰期未经保护就推送到生产环境。根因通常是缺少防护措施,而不是触发事件的操作。

追查表面原因之后的因素。 如果你的答案是“有人推送了错误配置”,你只指出了触发因素,然后就停下了。继续追问:什么导致未经审核的配置进入生产环境?为什么没有对连接池大小设置范围校验?为什么金丝雀发布没有发现问题?“五个为什么”等技巧有助于持续追问——但它们只是提示方法,不是证据;而且它们往往会引导你沿着单一因果链追查,实际情况却可能涉及多条链路。大多数事件都有多个促成因素和一两个根因,而不是一条线性的因果链。指出一两个这样的条件:如果这些条件不存在,触发因素就不会造成影响。


第 7 步 — 促成因素

这些条件扩大了影响范围、延迟了检测,或拖慢了恢复。它们不是根因,但正是这些因素导致事件变得如此严重——而且每个因素通常都应对应一个独立的改进行动项。

重点检查以下方面:

  • 告警缺失或路由错误 — 第 5 步中发现的检测延迟几乎总会归入这一类。
  • 运行手册缺失或过时 — 没有记录恢复流程,值班人员只能临场应对。检查 guides/ 及其中的 last_verified 日期。
  • 职责归属不明确 — 无人知道哪个团队负责故障组件,因此确认收到告警有所延迟。
  • 过时的指南 — 运行手册建议采取反而会加重问题的操作,或者其 last_verified 日期已过了很久。
  • 叠加条件 — 流量高峰、同时发生的发布冻结、已有另一位值班人员收到告警。

将每个因素分别列出。“沟通本可以做得更好”之类的笼统说法不算因素;“支付连接池没有明确的负责人映射,因此首次告警被发给了错误团队,延误了 7 分钟”才算。


步骤 8 — 哪些方面做得好(实事求是)

这一部分不是凑数,悄悄跳过它是一种失败模式。总有某些因素减轻了损失;把它们写出来,才能让组织保留下来。

  • 是什么让问题更快被发现或恢复?是出色的仪表板、快速回滚路径,还是清晰的升级流程?
  • 哪些东西在负载下经受住了考验?是能平稳卸载负载的断路器,还是吸收了流量的只读副本?
  • 哪项以往的行动项在这次事件中发挥了作用?如果之前的复盘所采取的措施限制了此次事件的影响,就明确写出来并附上链接——这是为行动项争取资金支持的最有力论据。

如实书写。如果确实没什么做得好的,就直说,而不是硬凑赞美——“恢复缓慢且依赖手动操作;唯一有效的是只需一条命令就能回滚。”保留行之有效的做法,与修复问题同样重要。


步骤 9 — 行动项:明确负责人、日期和验证方式

没有负责人的行动项只是愿望。每项都必须具备以下三点,否则就不要列入清单:

  • 负责人 — 一个职务或团队(不归咎于个人:“支付团队”,而非某个人的名字,除非此人主动认领)。
  • 截止日期 — 一个具体日期,而不是“尽快”。
  • 可验证的完成条件 — 可以检查并明确判定是否完成的事项。“当结账错误率连续 3 分钟超过 2% 时触发告警,并在预发布环境中通过主动触发进行测试”——而不是“改进告警”。

根据每项行动能带来的效果进行分类:

  • 预防 — 阻止同一问题再次发生(在配置流水线中添加连接池大小校验)。
  • 缓解 — 即使问题再次发生,也能限制损失(连接池耗尽时自动卸载负载)。
  • 检测 — 更早发现问题(步骤 5 中缺失的错误率告警)。

一份健康的复盘应包含这三类行动项;如果清单里全是预防措施,往往意味着检测和缓解方面的缺口没有得到审视。

符合指南结构的行动项会生成运行手册的初始版本。 如果行动项是“编写/修复 X 的恢复运行手册”,不要只列出名称——现在就先创建初始版本:

write({ document: { path: "guides/db-pool-exhaustion-runbook.md", template: "runbook" } })

在初始版本中预先填写症状(如何再次识别此问题——“结账请求返回 500,且连接池的空闲连接数为零”)和相关时间线摘录(从步骤 5 中摘取实际奏效的回滚步骤)。然后在行动项中链接该初始版本:- [ ] Payments team, by 2024-03-16: complete the [pool-exhaustion runbook](./guides/db-pool-exhaustion-runbook.md), verified by a tabletop dry-run. 运行手册包含 last_verified frontmatter,因此内容过期时会在审核中显现。


步骤 10 — 添加链接并验证

  • 添加 ## Related 部分,链接所有共享同一子系统的既往复盘(来自步骤 2),以及相关的 decisions/ 和 specs/——例如设定配置策略的决策,以及故障组件的规范。[2024-01-11 payments pool](./postmortems/2024-01-11-payments-pool.md)、[connection-pool sizing decision](./decisions/2023-11-04-pool-sizing.md)。
  • 在步骤 9 中创建的运行手册初始版本,要从相应行动项(如上所示)和 ## Related 中添加链接。
  • 如果你修改的文件夹有索引(例如 postmortems/ 中的 INDEX.md),请更新它的索引,让新事件显示在其中——预览内容便会成为实时进度条。
  • 验证:audit({ path: "postmortems/YYYY-MM-DD-name.md" }) 应无问题(包括所有 lint 违规和失效的内部链接)——修复每一项发现的问题。确认 frontmatter 完整,并且 Summary / Timeline / Root cause / What went well / Action items 各部分均已填写,没有任何模板占位符。

步骤 11 — 回顾

用以下四点简洁地结束对话:

  1. 影响:用一句话描述用户可见的影响。
  2. 根本原因:用一句话说明根本原因(即导致问题的条件,而非触发因素)。
  3. 最具杠杆效应的一项行动——通常是弥补检测缺口或补上缺失的防护措施。
  4. 这是否属于重复出现的问题类型——如果是,说明之前的修复为何未能奏效。

然后指出文件:postmortems/YYYY-MM-DD-name.md,以及创建的任何运行手册草稿。


非目标

  • 不要归咎于个人。 关注系统和角色,绝不针对个人。如果故事离不开某个姓名,说明故事的框架有问题——应找出系统层面的机制。
  • 不要止步于第一个看似合理的原因。 “有人推送了错误配置”是触发因素。继续追问是什么让这个触发因素足以造成问题,直到找到缺失的防护措施。
  • 不要写无法验证的行动项。 没有负责人、日期和可核验的完成条件,就只是愿望,而不是行动项。删除它,或补充完整。
  • 不要悄悄略过“进展顺利的方面”。 略过会让值得保留的做法失传。如果进展顺利的方面很少,就如实说明,不要删掉这一节。
  • 不要用事后复盘重新争论设计决策。 事后复盘记录发生了什么以及运营层面需要做哪些改变。关于架构是否应该有所不同的争论,应放在提案(/frame-a-proposal)或设计评审(/review-a-design)中——在“相关内容”中添加链接,不要在这里争论。
  • 不要编造时间线。 每条记录都必须有可指出的来源;根据记忆重建的记录必须在行内标明。

评论

支持 Markdown