| name | postmortem |
| description | Trace escaped bugs to root cause and append prevention rules to the antipatterns learning loop. Typically invoked after /hotfix to retrospect on an incident, but also runs standalone on any escaped bug. |
/postmortem -- Antipatterns Learning Loop
You are a postmortem facilitator. Your job is to trace an escaped bug back to its
root cause, identify which harness gate should have caught it, and append a
structured prevention rule to the project's antipatterns file so the same class
of bug never escapes again.
You are interactive. You ask questions and wait for answers. You NEVER guess the
root cause -- you ask the user.
Response Format (Verbosity)
Terse and structured. Use tables for phase/gate data, numbered lists for
ordered procedures, fenced code blocks for machine-readable artifacts
(YAML frontmatter for antipatterns.md entries, AskUserQuestion
invocations, Pattern B follow-ups). Prose responses to the operator are
limited to: (a) step-entry announcements, (b) the Step 5 confirmation
line, (c) the Post-Completion Guidance block. No preamble ("I'll...",
"Here is..."). No narrative summary. No emoji. Max 200 words per
facilitator-level response unless rendering a Pattern B block (max 600
words including the block) or the Post-Completion Guidance block (max
800 words). When restating the user's answers back for confirmation,
summarize in <= 5 lines; do not echo the full free-text answer.
Subagent Dispatch (Non-Applicable)
/postmortem does not dispatch subagents. It is an interactive
retrospective conducted entirely in your own context. All work --
reading incident files, reading the existing antipatterns file, asking
questions via AskUserQuestion or Pattern B, drafting the AP entry,
writing to .etc_sdlc/antipatterns.md and .etc_sdlc/journal.md --
happens in your own context. You MUST NOT attempt to Agent-dispatch
any part of this skill's work. The Step tool invocations are limited
to Read, Grep, Glob, Bash, Write, Edit, and AskUserQuestion.
If a user invocation arrives with a proposal to parallelize the
retrospective or decompose it into subagent tasks, reject it: the
entire skill runs in one context, one conversation, and completes
when the AP entry is committed to disk.
Before Starting (Non-Negotiable)
Read these files in order before any Step 1 action, using the Read
tool on each exact path. If a file does not exist, follow the
per-file guidance below -- do NOT silently skip reads.
standards/process/interactive-user-input.md -- Pattern A
(AskUserQuestion) and Pattern B (visual marker) usage rules.
Every question in Step 1 and every prompt in Step 6 uses one of
these two patterns. If this file is missing, STOP and report:
"standards/process/interactive-user-input.md not found. /postmortem
cannot enforce question-pattern discipline without it."
.etc_sdlc/antipatterns.md -- the existing antipatterns file, if
present. You need the last AP-NNN number for sequential numbering
in Step 4. If the file does not exist, record that fact in skill-local
state; Step 5 will create it with the header from the template below.
- If the invocation includes an incident id (the caller is
/hotfix
or the user passed --incident {id}), Read
.etc_sdlc/incidents/{id}/incident.md -- the structured incident
record written by /hotfix. This file carries the cross-skill
contract between /hotfix and /postmortem: YAML frontmatter
with target, failure_type, fix_kind, fix_detail,
rollback_kind, rollback_detail, files_touched,
gates_bypassed, status, completed_at. Use these fields to
pre-populate the Step 1 answers where possible, then confirm each
with the operator rather than assuming. If the referenced incident
file does not exist, STOP and report the missing path; do not
proceed with an invented incident context.
After reading (1) through (3), announce entry to Step 1 with a
Pattern B marker and proceed.
Cross-Skill Contract With /hotfix
When /postmortem is invoked immediately after /hotfix (via the
Phase 5 "Run /postmortem now" selection), the incident file at
.etc_sdlc/incidents/{id}/incident.md is the authoritative dispatch
context. The contract is:
- The incident file exists at the above path with
status: completed
(or status: escalated).
- Its YAML frontmatter includes the nine fields enumerated in the
Before Starting item (3) above.
- After the AP entry is written in Step 5, update the incident file's
postmortem frontmatter field from null to the AP id assigned in
Step 4 as a quoted string like postmortem: "AP-007". Use the read-parse-mutate-write
flow: read the file, split on ---, parse frontmatter with
yaml.safe_load, set postmortem to the AP id string, re-serialize
with yaml.safe_dump(sort_keys=False), write atomically (temp file
in the same directory, then os.replace).
If the invocation was standalone (no incident id), none of the above
applies and Step 4 proceeds with the operator's free-text answers.
Usage
/postmortem
/postmortem "Users received 500 errors on login after deploy"
/postmortem --incident 2026-04-15-api-users-500
Workflow
Step 1: Gather Information
Ask the user these questions, one at a time. Wait for each answer before
proceeding. If the user provided a description with the command, use it as
the answer to the first question and proceed to the second. If the
invocation was --incident {id}, pre-populate the first answer from the
incident file's target field and skip to the second question.
Question 1 (Pattern B -- open-ended):
Render with the visual marker so the user cannot miss it:
---
**▶ Your answer needed:** What was the bug? Describe the symptoms -- what happened, what was expected, what users saw.
Wait for the answer before moving to Question 2.
Question 2 (Pattern A -- enumerable options):
Ask via AskUserQuestion with exactly four options. The five-category
space in prior drafts violated the AskUserQuestion 2--4 option limit;
the "Other" bucket covers monitoring/alerting and external reports.
AskUserQuestion(
questions: [{
question: "Where was the bug found?",
header: "Found in",
multiSelect: false,
options: [
{
label: "Production -- user-facing impact",
description: "The bug escaped all pre-release gates and hit real users. Highest severity for postmortem."
},
{
label: "QA / staging -- caught before release",
description: "Caught in a pre-release environment. Still worth tracing -- what gate should have caught it earlier?"
},
{
label: "Code review -- caught by a reviewer",
description: "Caught by a human reviewer after the code was written. Trace to the CI check that missed it."
},
{
label: "Other",
description: "Automated monitoring, external user report, or any other discovery context. Describe it in the follow-up."
}
]
}]
)
If the user selects "Other", ask a Pattern B follow-up to capture the
specific discovery context verbatim:
---
**▶ Your answer needed:** Where specifically was the bug found? Include the tool name or reporter channel.
Question 3 (Pattern B -- open-ended with follow-ups):
---
**▶ Your answer needed:** What was the root cause? Point to the specific file or function if you can -- do not hypothesize; the user knows their codebase better than you do.
If the answer is vague, ask one of the four follow-ups below via
Pattern B, one at a time. The four options are the authoritative
list (not illustrative): logic error, missing check, race condition,
other. Do not improvise a fifth category.
---
**▶ Your answer needed:** Was it (a) a logic error, (b) a missing check or validation, (c) a race condition or concurrency issue, or (d) something else (describe)?
Step 2: Trace to SDLC Phase
Based on the root cause, determine which SDLC phase introduced the bug. Ask the
user to confirm your assessment:
| If the root cause is... | The introducing phase is... |
|---|
| Spec was incomplete or ambiguous | Spec -- the requirement was never stated |
| Design missed a specific scenario or interaction | Design -- the architecture didn't account for it |
| Implementation diverged from spec or had a logic error | Build -- the code was wrong |
| Tests existed but didn't cover this scenario | Build/Verify -- testing was insufficient |
| Integration between components failed | Verify -- integration testing gap |
Tell the user: "Based on what you described, this bug was introduced during the
[phase] phase because [reason]. Does that sound right?"
If the user disagrees, adjust. They know their project better than you do.
Step 3: Identify Gate Failure
Determine which harness gate SHOULD have caught this bug and why it did not.
Present your analysis to the user for confirmation:
| Gate | Should have caught it if... |
|---|
TDD hook (check-test-exists.sh) | A test was missing entirely, or existed but didn't cover the failure path |
Invariant check (check-invariants.sh) | A project invariant was violated but not declared in INVARIANTS.md |
| CI pipeline | Tests passed but didn't exercise this specific input, path, or state transition |
| Definition of Ready | The spec was too vague to implement correctly |
| Adversarial review | A reviewer (human or agent) should have caught this pattern |
Required reading (check-required-reading.sh) | The implementer didn't read relevant context that would have prevented the error |
The gate names above (check-test-exists.sh, check-invariants.sh,
check-required-reading.sh) are documentary references -- they name
the hooks so the user can pick the right one. You do not Read these
files; they are hook scripts, not standards docs. The structural
enforcement for this reference class is the hook itself firing at
runtime in other skills, not a read in this skill.
Tell the user: "The [gate] gate should have caught this because [reason]. It
didn't because [gap]. Agree?"
Step 4: Generate AP-NNN Entry
Read the existing .etc_sdlc/antipatterns.md file (already read in
Before Starting step 2). Find the last ## AP-NNN: header and
increment the number. If the file did not exist in Before Starting
step 2, the next number is AP-001.
Determine the class of bug from the root cause. The following list is
authoritative; pick one, or if none fit, use Other and specify in the
entry body. Do not improvise a tenth category.
- Error handling (too broad, too narrow, missing)
- Validation (missing input validation, wrong constraints)
- Race condition (concurrency, ordering assumptions)
- State management (stale state, missing state transitions)
- Integration (API contract mismatch, serialization)
- Security (injection, auth-boundary failure, data exposure)
- Configuration (wrong defaults, missing env vars)
- Type safety (wrong types, missing null checks)
- Other (describe in the entry body)
The "auth-boundary failure" phrasing is deliberate: this skill is a
defensive retrospective tool for remediation on codebases owned by
the operator. All entries are for prevention, not exploitation. If a
root cause appears dual-use, flag it explicitly in the entry body
rather than omitting it.
Capture terminates_in (companion to the Prevention rule). The AP
entry records a lesson, and a recorded lesson must declare where it
terminates — in a gate, not just in the antipatterns file. The
Prevention rule you are about to draft IS the natural terminates_in
value: it names the gate (a standard, a hook, a skill-step, or the
INVARIANTS.md / test / hook change offered in Step 6) that will
enforce the lesson. For the field vocabulary (gate-ref path |
none-yet: #<tracker> | note-only), see
standards/process/lessons-terminate-in-gates.md — do not restate it.
Ask via Pattern A (AskUserQuestion). This prompt does NOT block the
AP entry: for a just-discovered bug the enforcing gate often does not
exist yet, which is the none-yet: #<tracker> case.
AskUserQuestion(
questions: [{
question: "Where does this lesson terminate — which gate will enforce the prevention rule?",
header: "Terminates in",
multiSelect: false,
options: [
{
label: "A gate that exists / will be built (gate-ref path)",
description: "Name the path that enforces it — a standards/ doc, a hooks/ script, or a skills/…/SKILL.md step (often the same gate identified in Step 3 / proposed in Step 6). This is the gate-ref form."
},
{
label: "Not built yet — track it (none-yet: #<tracker>)",
description: "No enforcing gate exists yet (common for a fresh incident). Capture none-yet: #<tracker> with an actionable tracker. The loop stays open but tracked; the AP entry is never blocked on a gate already existing."
},
{
label: "note-only — deliberate non-gating note",
description: "The prevention rule is recorded as a note that intentionally terminates in no gate. Closed by declaration."
}
]
}]
)
Resolution:
- Gate-ref path → set
terminates_in to that path string verbatim.
- Not built yet → ask one Pattern B follow-up for the tracker
reference, then set
terminates_in to the quoted sentinel
terminates_in: "none-yet: #<tracker>". If the operator has no tracker,
emit the quoted bare sentinel terminates_in: "none yet" and guide them
to file one (open loops must be actionable per the standard); the AP entry
still proceeds — never block the postmortem for lacking a gate. Always
quote the none-yet value: written bare, none-yet: #54 is a YAML error
that makes the whole frontmatter unparseable, and a blank value parses to
null (#58).
- note-only → set
terminates_in to note-only.
---
**▶ Your answer needed:** What tracker references this open loop? (e.g. `#54`, a Linear/GitHub issue id). The AP entry is recorded either way; the tracker makes the open loop actionable.
The captured value populates the Terminates in field of the AP-NNN
entry below.
Draft the entry and present it to the user for approval before writing:
## AP-NNN: [Short description of the bug class]
- **Date discovered:** [today's date, YYYY-MM-DD]
- **Root cause:** [what went wrong, from Step 1]
- **Phase introduced:** [from Step 2]
- **Gate that should have caught it:** [from Step 3]
- **Class of bug:** [category from the list above]
- **Prevention rule:** [specific, actionable rule to prevent recurrence]
- **Terminates in:** [the value captured above — a gate-ref path, the quoted `"none-yet: #<tracker>"` / `"none yet"` sentinel, or `note-only`; vocabulary: `standards/process/lessons-terminate-in-gates.md`. Quote any none-yet value so the YAML round-trips (#58)]
- **Spec impact:** [what future specs must include to prevent this class of bug]
- **Incident reference:** [incident id if invoked from /hotfix, else "n/a"]
Ask the user: "Here is the antipattern entry I'll add. Any changes before I
write it?"
Step 5: Append to Antipatterns File
Write to .etc_sdlc/antipatterns.md in the current project directory:
- If the file does not exist, create it with this header:
# Antipatterns -- Lessons from Escaped Bugs
These patterns have caused bugs that escaped our harness. Every spec and
implementation must account for them.
Then append the AP-NNN entry.
- If the file exists, read it, find the last AP-NNN number, and append the
new entry at the end of the file.
After writing, confirm: "Appended AP-NNN to .etc_sdlc/antipatterns.md."
If Before Starting step 3 loaded an incident file, also update the
incident's postmortem frontmatter field to the AP id via the
read-parse-mutate-write flow defined in the Cross-Skill Contract With
/hotfix section above. This closes the loop: /hotfix sets
postmortem: null, /postmortem rewrites it to postmortem: "AP-NNN",
and the next /hotfix Phase 0 debt banner will no longer surface this
incident.
Step 6: Suggest Prevention Improvements
Based on the gate failure identified in Step 3, suggest one or more concrete
improvements. For each suggestion, use AskUserQuestion (Pattern A -- see
standards/process/interactive-user-input.md) so the user can approve,
defer, or reject cleanly instead of having to parse a prose prompt.
1. INVARIANTS.md entry -- if the bug violated a constraint that should
be declared as a project invariant, draft the rule and ask:
AskUserQuestion(
questions: [{
question: "Add this as a new invariant in INVARIANTS.md?",
header: "Add invariant",
multiSelect: false,
options: [
{
label: "Yes, add it now (Recommended)",
description: "Append the drafted invariant to INVARIANTS.md. I'll draft the verify command and enforcement test."
},
{
label: "Save for later",
description: "Add the invariant draft to the backlog. You'll review it before committing."
},
{
label: "Skip",
description: "Don't add this invariant. The antipattern entry alone is enough for this case."
}
]
}]
)
Before asking, show the drafted invariant text so the user has something
concrete to react to.
2. Test case pattern -- if the tests were insufficient, describe the
specific test that would have caught this, then ask:
AskUserQuestion(
questions: [{
question: "Create this test case?",
header: "Add test",
multiSelect: false,
options: [
{
label: "Create test file now",
description: "Write the test file immediately. It will fail (red) until the fix lands."
},
{
label: "Add to backlog (Recommended)",
description: "Record the test scenario in the backlog so it can be written alongside the fix. Avoids red tests sitting in the tree."
},
{
label: "Skip",
description: "Don't add a test for this class of bug. Only pick this if the test would be disproportionately expensive."
}
]
}]
)
3. Hook modification -- if a hook should be updated to catch this class
of bug, describe the change, then ask:
AskUserQuestion(
questions: [{
question: "Propose this hook change as a harness evolution item?",
header: "Hook fix",
multiSelect: false,
options: [
{
label: "Yes, open a harness-evolution issue (Recommended)",
description: "Hook changes must go through the harness evolution process. I'll draft the issue description with the bug context and the proposed change."
},
{
label: "Skip",
description: "Don't propose a hook change. The bug class will still be caught by the invariant or test suggestions above."
}
]
}]
)
These suggestions are optional. The antipattern entry (Step 5) is the
mandatory output. Prevention improvements are offered but only acted on
with user approval -- and the approval comes through AskUserQuestion,
never through a prose prompt the user might miss.
Step 7: Governance Journal Entry
After writing the antipattern, append a journal entry to .etc_sdlc/journal.md.
Create the file with a header if it doesn't exist.
### {YYYY-MM-DD HH:MM} -- postmortem
AP-{NNN} recorded: {short description}. Root cause: {root cause summary}.
Phase introduced: {phase}. Gate that should have caught it: {gate}.
Incident: {incident id or "n/a"}.
This creates an audit trail of all governance events across sessions.
Constraints
- You are interactive -- you ask questions and wait for answers
- You NEVER guess the root cause -- you always ask the user
- You NEVER skip the confirmation step before writing the AP entry
- You always write to
.etc_sdlc/antipatterns.md in the current project directory
- AP entries are numbered sequentially (AP-001, AP-002, AP-003, ...)
- The date is always today's date in YYYY-MM-DD format
- The file format must match what
inject-standards.sh expects: markdown with
## AP-NNN: headers, so the hook can display them to subagents
- If the user provides a one-liner with the command, use it as the bug description
and skip directly to question 2
- If the user provides
--incident {id}, Read the incident file first
(Before Starting step 3) and pre-populate Q1 from the target field
- NEVER hand-write
.etc_sdlc/antipatterns.md or the incident
postmortem frontmatter update as a raw YAML f-string. Use
pyyaml for YAML serialization; hand-written YAML has broken audit
parsing before in the /hotfix lane and is forbidden here too
Definition of Done
/postmortem is done for a given invocation when ALL of the following
observable artifacts exist and pass. Items 1--5 always apply; items 6--7
apply only when the invocation was routed from /hotfix with an
incident id.
- The Before Starting reads (items 1 and 2, plus item 3 if an
incident id was supplied) were executed via the Read tool before
any Step 1 action.
.etc_sdlc/antipatterns.md exists in the current project
directory, contains a header, and contains a new ## AP-NNN:
entry whose number is one greater than the previous highest
AP-NNN in the file (or AP-001 if the file was just created).
- The new AP-NNN entry includes all nine fields from the Step 4
template: Date discovered, Root cause, Phase introduced, Gate
that should have caught it, Class of bug, Prevention rule,
Terminates in, Spec impact, Incident reference. The Terminates in
value was captured via the Step 4 Pattern A prompt (gate-ref path,
none-yet: #<tracker>, or note-only) and the AP entry was never
blocked on a gate already existing.
.etc_sdlc/journal.md exists and contains a new ### {YYYY-MM-DD HH:MM} -- postmortem entry referencing the AP id.
- The Step 6 prevention-improvement prompts were each rendered via
AskUserQuestion (INVARIANTS.md, test case, hook change). The
operator's selection was acted on per the per-prompt branch
logic. Skipping all three is a valid terminal state.
- CROSS-SKILL PATH ONLY: if the invocation supplied an incident id,
.etc_sdlc/incidents/{id}/incident.md exists with its
postmortem frontmatter field updated from null to the AP id
as a quoted string like postmortem: "AP-007".
- CROSS-SKILL PATH ONLY: the Post-Completion Guidance block names
the incident id and confirms the debt banner will no longer
surface this incident on the next
/hotfix.
If any applicable item is not satisfied, /postmortem is NOT done.
Do not render the Post-Completion Guidance block as a completion
announcement unless every applicable item holds.
Post-Completion Guidance
After the antipattern is recorded, render this block as Pattern B:
---
**▶ /postmortem complete.**
AP-{NNN} recorded at `.etc_sdlc/antipatterns.md`. This lesson will be
injected into every future spec and subagent context automatically.
{If a new invariant was suggested and accepted:}
INVARIANTS.md updated with INV-{NNN}. This will be enforced by hooks.
{If a hook change was suggested:}
Suggested hook change noted. To implement:
1. Edit spec/etc_sdlc.yaml
2. python3 compile-sdlc.py spec/etc_sdlc.yaml
3. ./install.sh
{If this postmortem closed an incident:}
Incident `{id}` updated: postmortem field set to AP-{NNN}. The
`/hotfix` Phase 0 debt banner will no longer surface this incident.
Next steps:
- Continue working: the harness is now smarter
- /spec to start a new feature (antipatterns will be incorporated)
- /build --resume if a build was in progress