Postmortem Autobiographer
1. System Architecture & Prerequisites
Every incident deserves a review, but blank-page postmortems never get written.
The Autobiographer takes raw material — an incident description, a stack trace
paste, timestamps, slack snippets — and composes a structured, blame-free
document following industry conventions (Google SRE model): summary, timeline,
impact, root cause via 5-why, and action items with owners and deadlines.
Node 18+ stdlib.
Input formats recognized:
- Stack traces (V8, Python, generic) — service/exception extracted
- Free-form incident notes (timestamps
YYYY-MM-DD HH:MM, @owner, action)
- Structured JSON
{ title, started, ended, summary, services[] }
2. Input/Output Data Contracts
Input: a text file and optional starter JSON. Output POSTMORTEM.md (plus raw
postmortem.json) with sections:
# Postmortem: <title> + status (DRAFT)
## Summary / ## Timeline / ## Impact
## Root Cause (5-Why) — five chained why rows
## Action Items — - [ ] checklist with owner: and by: metadata
## Prevention Notes
Exit: 0 always on successful generation; 2 if no usable material.
3. Production Reference Implementation
// postmortem-autobiographer.js
const fs = require('fs');
const path = require('path');
const inputFile = process.argv[2];
const title = process.argv[3] || 'Incident Review';
if (!inputFile) { console.error('usage: node postmortem-autobiographer.js <incident.txt|incident.json> [title]'); process.exit(2); }
const raw = fs.readFileSync(inputFile, 'utf-8');
function parseStack(raw) {
const lines = raw.split(/\r?\n/);
const exception = lines.find((l) => /(Error|Exception|Traceback|failed|FATAL)/i.test(l))?.trim() || 'Unknown exception';
const frames = lines.filter((l) => /^\s+at\s|^\s+File\s"/.test(l)).slice(0, 8).map((l) => l.trim());
const first = frames[0] || exception;
return { exception, firstFrame: first, frames };
}
function parseNotes(raw) {
const timeline = [];
const re = /(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(?::\d{2})?)/g;
let m;
while ((m = re.exec(raw))) timeline.push({ time: m[1] });
const owners = [...new Set((raw.match(/@(\w+)/g) || []).map((o) => o.slice(1)))];
const actions = (raw.match(/^(?:-\s*\[\s*\]|action:?)\s*(.+)$/gim) || []).map((a) => a.replace(/^-\s*\[\s*\]\s*/, '').replace(/^action:?\s*/i, ''));
return { timeline, owners, actions };
}
let input = { title, summary: '', body: raw };
try { const j = JSON.parse(raw); input = { ...input, ...j }; } catch {}
const st = parseStack(raw);
const notes = parseNotes(raw);
// Craft 5-whys from the evidence (heuristic chain, human confirms each level).
const whys = [];
const problem = input.summary || st.exception || 'Unexpected failure in production';
whys.push(`1. **Why did ${problem} occur?** — Direct cause observed: ${st.firstFrame || 'no frame captured; see timeline'}.`);
whys.push('2. **Why was the direct cause possible?** — Guard/validation missing or ordering wrong (verify against code).');
whys.push('3. **Why was the guard missing?** — Assumption made earlier; not encoded as a test/budget/boundary check.');
whys.push('4. **Why was the assumption not encoded?** — Change-management/review gap; refactor without characterization coverage.');
whys.push('5. **Why did that persist?** — Missing feedback loop (alerting/detection) surfaced it only after user impact.');
const actionItems = (notes.actions.length ? notes.actions : [
'Add characterization test reproducing the failure path',
'Add alert/budget gate so a recurrence trips before user impact',
]).map((a, i) => {
const owner = notes.owners[i % Math.max(1, notes.owners.length)] || 'TBD';
const by = new Date(Date.now() + 5 * 86400000).toISOString().slice(0, 10);
return `- [ ] ${a} \`owner: ${owner}\` \`by: ${by}\``;
});
const timelineMd = notes.timeline.length
? notes.timeline.map((t, i) => `- \`${t.time}\` step ${i + 1} (event recorded in notes)`).join('\n')
: '- (no timestamps recovered — reconstruct from logs)';
const md = [
`# Postmortem: ${input.title}`, '',
`**Status:** DRAFT · **Severity:** pending`, '',
'## Summary', '', input.summary || 'Production impact event(s) detected; see timeline and evidence.', '',
'## Timeline', '', timelineMd, '',
'## Impact', '', '- Affected service/function: to be filled', '- User-visible impact: to be filled', '- Metrics (error rate/latency): to be filled', '',
'## Root Cause (5-Why)', '', ...whys, '',
'## Evidence', '', `- First frame: \`${st.firstFrame}\``, ...st.frames.slice(0, 4).map((f) => `- Frame: \`${f}\``), '',
'## Action Items', '', ...actionItems, '',
'## Prevention Notes', '', '- Verify 5-whys line 2 against the actual guard', '- Link monitoring dashboards + runbook', '- Set severity and owner; schedule review',
].join('\n');
fs.writeFileSync('POSTMORTEM.md', md);
fs.writeFileSync('postmortem.json', JSON.stringify({ title: input.title, problem, owners: notes.owners, actions: actionItems }, null, 2));
console.log(`Postmortem written: POSTMORTEM.md (${actionItems.length} action items; owners: ${notes.owners.join(', ') || 'TBD'})`);
process.exit(0);
4. Execution Protocol & Step-by-Step Workflow
- Collect the incident material into one file: stack trace dump, timestamps,
@owner tags, and any explicit action lines.
node postmortem-autobiographer.js incident.txt "API latency spike Sep 12".
- Review the generated
POSTMORTEM.md and fill the Impact placeholders —
the tool extracts what it can extract; market impact must come from humans.
- Walk the 5-whys chain with the actual guard in code: level 2 is the load
bearing one — verify which check was missing by reading the diff/deployment.
- Assign owners to every action item, blast it in the channel, track closed
items in the review, and move the status from DRAFT once resolved.
- Store the final
POSTMORTEM.md alongside the team's incident reviews;
link it from the monitoring dashboard.
5. Edge Cases & Error Handling
- No timestamps recovered → the timeline section politely says so and asks for
reconstruction, instead of fabricating events.
- No owners/actions recovered → defaults to
TBD owner and a 5-day by date,
but the empty-places force human completion rather than silent GTM.
- Malformed JSON input falls back to treating the text as free-form notes; the
document is still generated.
- Multi-error stack traces keep only the first exception for the "problem"
line but list extra frames as evidence.
- The 5-whys are hypotheses the team must verify — the report is designed for
a group review, not as a finished accountability document.
1---2name: postmortem-autobiographer3description: Turn an incident description or production stack trace into a structured postmortem with timeline, 5-whys, and trackable action items — ready for team review.4---56# Postmortem Autobiographer78## 1. System Architecture & Prerequisites910Every incident deserves a review, but blank-page postmortems never get written.11The Autobiographer takes raw material — an incident description, a stack trace12paste, timestamps, slack snippets — and composes a structured, blame-free13document following industry conventions (Google SRE model): summary, timeline,14impact, root cause via 5-why, and action items with owners and deadlines.15Node 18+ stdlib.1617Input formats recognized:1819- Stack traces (V8, Python, generic) — service/exception extracted20- Free-form incident notes (timestamps `YYYY-MM-DD HH:MM`, `@owner`, `action`)21- Structured JSON `{ title, started, ended, summary, services[] }`2223## 2. Input/Output Data Contracts2425Input: a text file and optional starter JSON. Output `POSTMORTEM.md` (plus raw26`postmortem.json`) with sections:2728- `# Postmortem: <title>` + status (DRAFT)29- `## Summary` / `## Timeline` / `## Impact`30- `## Root Cause (5-Why)` — five chained why rows31- `## Action Items` — `- [ ]` checklist with `owner:` and `by:` metadata32- `## Prevention Notes`3334Exit: `0` always on successful generation; `2` if no usable material.3536## 3. Production Reference Implementation3738```js39// postmortem-autobiographer.js40const fs = require('fs');41const path = require('path');4243const inputFile = process.argv[2];44const title = process.argv[3] || 'Incident Review';45if (!inputFile) { console.error('usage: node postmortem-autobiographer.js <incident.txt|incident.json> [title]'); process.exit(2); }46const raw = fs.readFileSync(inputFile, 'utf-8');4748function parseStack(raw) {49 const lines = raw.split(/\r?\n/);50 const exception = lines.find((l) => /(Error|Exception|Traceback|failed|FATAL)/i.test(l))?.trim() || 'Unknown exception';51 const frames = lines.filter((l) => /^\s+at\s|^\s+File\s"/.test(l)).slice(0, 8).map((l) => l.trim());52 const first = frames[0] || exception;53 return { exception, firstFrame: first, frames };54}5556function parseNotes(raw) {57 const timeline = [];58 const re = /(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(?::\d{2})?)/g;59 let m;60 while ((m = re.exec(raw))) timeline.push({ time: m[1] });61 const owners = [...new Set((raw.match(/@(\w+)/g) || []).map((o) => o.slice(1)))];62 const actions = (raw.match(/^(?:-\s*\[\s*\]|action:?)\s*(.+)$/gim) || []).map((a) => a.replace(/^-\s*\[\s*\]\s*/, '').replace(/^action:?\s*/i, ''));63 return { timeline, owners, actions };64}6566let input = { title, summary: '', body: raw };67try { const j = JSON.parse(raw); input = { ...input, ...j }; } catch {}6869const st = parseStack(raw);70const notes = parseNotes(raw);7172// Craft 5-whys from the evidence (heuristic chain, human confirms each level).73const whys = [];74const problem = input.summary || st.exception || 'Unexpected failure in production';75whys.push(`1. **Why did ${problem} occur?** — Direct cause observed: ${st.firstFrame || 'no frame captured; see timeline'}.`);76whys.push('2. **Why was the direct cause possible?** — Guard/validation missing or ordering wrong (verify against code).');77whys.push('3. **Why was the guard missing?** — Assumption made earlier; not encoded as a test/budget/boundary check.');78whys.push('4. **Why was the assumption not encoded?** — Change-management/review gap; refactor without characterization coverage.');79whys.push('5. **Why did that persist?** — Missing feedback loop (alerting/detection) surfaced it only after user impact.');8081const actionItems = (notes.actions.length ? notes.actions : [82 'Add characterization test reproducing the failure path',83 'Add alert/budget gate so a recurrence trips before user impact',84]).map((a, i) => {85 const owner = notes.owners[i % Math.max(1, notes.owners.length)] || 'TBD';86 const by = new Date(Date.now() + 5 * 86400000).toISOString().slice(0, 10);87 return `- [ ] ${a} \`owner: ${owner}\` \`by: ${by}\``;88});8990const timelineMd = notes.timeline.length91 ? notes.timeline.map((t, i) => `- \`${t.time}\` step ${i + 1} (event recorded in notes)`).join('\n')92 : '- (no timestamps recovered — reconstruct from logs)';9394const md = [95 `# Postmortem: ${input.title}`, '',96 `**Status:** DRAFT · **Severity:** pending`, '',97 '## Summary', '', input.summary || 'Production impact event(s) detected; see timeline and evidence.', '',98 '## Timeline', '', timelineMd, '',99 '## Impact', '', '- Affected service/function: to be filled', '- User-visible impact: to be filled', '- Metrics (error rate/latency): to be filled', '',100 '## Root Cause (5-Why)', '', ...whys, '',101 '## Evidence', '', `- First frame: \`${st.firstFrame}\``, ...st.frames.slice(0, 4).map((f) => `- Frame: \`${f}\``), '',102 '## Action Items', '', ...actionItems, '',103 '## Prevention Notes', '', '- Verify 5-whys line 2 against the actual guard', '- Link monitoring dashboards + runbook', '- Set severity and owner; schedule review',104].join('\n');105106fs.writeFileSync('POSTMORTEM.md', md);107fs.writeFileSync('postmortem.json', JSON.stringify({ title: input.title, problem, owners: notes.owners, actions: actionItems }, null, 2));108console.log(`Postmortem written: POSTMORTEM.md (${actionItems.length} action items; owners: ${notes.owners.join(', ') || 'TBD'})`);109process.exit(0);110```111112## 4. Execution Protocol & Step-by-Step Workflow1131141. Collect the incident material into one file: stack trace dump, timestamps,115 `@owner` tags, and any explicit action lines.1162. `node postmortem-autobiographer.js incident.txt "API latency spike Sep 12"`.1173. Review the generated `POSTMORTEM.md` and fill the `Impact` placeholders —118 the tool extracts what it can extract; market impact must come from humans.1194. Walk the 5-whys chain with the actual guard in code: level 2 is the load120 bearing one — verify which check was missing by reading the diff/deployment.1215. Assign owners to every action item, blast it in the channel, track closed122 items in the review, and move the status from DRAFT once resolved.1236. Store the final `POSTMORTEM.md` alongside the team's incident reviews;124 link it from the monitoring dashboard.125126## 5. Edge Cases & Error Handling127128- No timestamps recovered → the timeline section politely says so and asks for129 reconstruction, instead of fabricating events.130- No owners/actions recovered → defaults to `TBD` owner and a 5-day `by` date,131 but the empty-places force human completion rather than silent GTM.132- Malformed JSON input falls back to treating the text as free-form notes; the133 document is still generated.134- Multi-error stack traces keep only the first exception for the "problem"135 line but list extra frames as evidence.136- The 5-whys are *hypotheses* the team must verify — the report is designed for137 a group review, not as a finished accountability document.