Módulo 7: Blameless Postmortems And Runbooks
3. Manos a la obra: escribiendo el postmortem del incidente Claude Code
Descripción
Este es el documento central de toda esta guía. TIMELINE.md (Módulo 6) ya contestó qué pasó, con qué severidad, con qué roles, y a qué costo medido en error budget. Esta lección contesta la pregunta que ese documento dejó deliberadamente abierta: por qué pasó, a nivel de sistema, siguiendo la estructura real de la plantilla de postmortem de Google SRE —resumen, impacto, causa raíz distinguida de disparador, qué salió bien, qué salió mal, action items— aplicada al criterio sin culpa que la lección 2 acaba de establecer.
Conexión con el módulo
POSTMORTEM.md cita TIMELINE.md, nunca lo repite —el mismo principio de ensamblaje disciplinado que gobierna cada documento de esta guía desde SLO.md—. La sección de causa raíz de este documento se apoya, punto por punto, en la cadena de siete fallos que cloud-security-and-guardrails-guide, Módulo 1, lección 6 ya construyó para este mismo incidente. La lección 4 de este módulo toma la tabla breve de action items que este documento deja al final y la expande con criterio SMART completo: dueño, prioridad, criterio de verificación.
Paso 1 — Por qué "causa raíz" y "disparador" son dos secciones, no una
La plantilla de postmortem de Google SRE distingue, con precisión, dos preguntas que suenan parecidas pero no lo son: el disparador (trigger) es el evento específico que puso en marcha el incidente; la causa raíz (root cause) es la condición sistémica que hizo posible que ese disparador causara el daño que causó. Colapsar las dos en una sola sección produce un postmortem que "arregla" el disparador —en este caso, sería tentador escribir "el problema fue el state desactualizado"— y deja el sistema exactamente tan expuesto como antes, porque el próximo disparador nunca será exactamente el mismo state desactualizado. Será otra cosa —una credencial expuesta, un plan mal leído, un campo de configuración equivocado— pero, si la causa raíz sistémica sigue sin resolverse, el resultado será el mismo tipo de incidente otra vez.
DISPARADOR vs CAUSA RAIZ -- POR QUE "ARREGLAR EL DISPARADOR" NO BASTA
DISPARADOR (este incidente especifico) CAUSA RAIZ (el sistema, en general)
───────────────────────────────────── ─────────────────────────────────
Un archivo .tfstate desactualizado Ningun control automatico, no
se carga como si fuera el estado dependiente de atencion humana,
real de la infraestructura se interpone entre "una accion
destructiva se propone" y
"se ejecuta"
│ │
▼ ▼
"Arreglar" esto: validar el "Arreglar" esto: un gate de
archivo especifico antes de politica (conftest) + una
cargarlo -- resuelve ESTE proteccion de recurso
disparador exacto (prevent_destroy) -- resuelve
CUALQUIER disparador que
│ intente la misma clase de daño
▼ │
El proximo disparador distinto ▼
(otro tipo de error) NO queda El proximo disparador distinto
cubierto por esta correccion SI queda bloqueado, porque el
control no depende de saber
de antemano cual sera
Paso 2 — El documento completo
En incidents/2026-02-26-claude-code-destroy/, junto a TIMELINE.md, crea POSTMORTEM.md:
# POSTMORTEM.md — 2026-02-26 Claude Code `destroy` Incident
**Status:** Final · **Severity:** SEV1 · **Postmortem owner:** Ana (Incident Commander)
**Real incident, external:** happened to DataTalks.Club, not to Andes Cargo. Analyzed here as a
drill against Andes Cargo's own reliability framework — see Module 6, lesson 1 and Module 7,
lesson 1 for the full honesty note on this distinction.
**Built on:** `incidents/2026-02-26-claude-code-destroy/TIMELINE.md` (Module 6, lesson 8). This
document does not re-derive any fact already established there — it cites, never repeats.
**Sources:** Alexey Grigorev, "How I Dropped Our Production Database" (alexeyondata.substack.com);
Hacker News #47278720; incidentdatabase.ai #1424; Tom's Hardware; `cloud-security-and-guardrails-
guide`, Module 1, lesson 6 (the seven-step failure chain this document's root cause section maps
onto).
## Summary
On 2026-02-26, an AI coding agent (Claude Code) executed `terraform destroy -auto-approve`
against DataTalks.Club's production infrastructure, using a stale state file as its only source
of truth. The agent had explicitly flagged the destructive action as risky before running it; the
human operator approved it anyway. VPC, ECS cluster, load balancers, bastion host, and the RDS
database — including all automated snapshots — were destroyed in a single command. The
`courses_answer` table (1,943,200 rows, 2.5 years of data) was gone. Recovery took exactly 24
hours and depended on an AWS-side snapshot that was not visible in the customer's own console.
## Impact
- **Duration:** 24 hours 0 minutes, `T+0` to `T+24h00m` (`TIMELINE.md`).
- **Scope:** total infrastructure loss — every service DataTalks.Club ran was unreachable from
`T+0` until partial restoration began.
- **Data:** `courses_answer`, 1,943,200 rows (2.5 years of student submissions, projects, and
course leaderboards), unrecoverable through any means under the team's own control.
- **Cost:** upgrade to AWS Business Support tier (~+10% of monthly cloud spend) to escalate the
recovery request.
- **Error budget (measured against Andes Cargo's own `SLO.md`, Module 6, lesson 7):** 1,440
minutes consumed against a 43.2-minute monthly budget — **33.3x** the full monthly budget in a
single event, a -1,396.8-minute deficit. Burn rate itself is not calculable (Module 6, lesson 3
and lesson 7): no infrastructure remained to generate measurable traffic once `T+0` executed.
- **Users:** the primary source does not report a precise affected-user count; the destroyed data
represents 2.5 years of accumulated course activity on a platform serving DataTalks.Club's full
learner base. This document does not fabricate a number the source does not provide.
## Detection
Immediate and total — every service became unreachable the instant `terraform destroy
-auto-approve` completed (`TIMELINE.md`, `T+0` to `T+~5min`). No monitoring system "detected" this
incident in the sense of an alert firing on a threshold: the infrastructure that would have hosted
any such alerting system was destroyed in the same command. Detection here was direct observation
of total outage, not instrumentation — a limit already named and analyzed in Module 6, lesson 3
(burn rate is mathematically undefined, `0/0`, when no traffic-generating infrastructure remains).
## Resolution
Summarized here; full branch-by-branch detail lives in `TIMELINE.md`'s "Mitigation decision tree"
section (Module 6, lesson 6). Two self-service recovery paths were checked and exhausted within
minutes: reverting via Terraform state (not applicable — the state itself was the cause, and the
infrastructure it described no longer existed) and locating an automated RDS snapshot in the
account's own console (none — destroyed alongside the database). The team escalated to AWS
Business Support (`T+~1h`); AWS confirmed an internal snapshot not listed in the console
(`T+~1h30min` — the single point of genuine uncertainty in the entire incident); phone escalation
followed (`T+~2h` to `T+~3h`); AWS restored the snapshot at `T+24h00m`, with all 1,943,200 rows
confirmed intact.
## Root cause(s) and trigger
Google SRE's postmortem template distinguishes a **trigger** (the specific event that set the
incident in motion) from **root cause(s)** (the systemic conditions that made the trigger possible,
or that let it cause the damage it caused). Collapsing the two into one produces a postmortem that
"fixes" the trigger and leaves the system exactly as exposed as before.
**Trigger:** during a routine task (migrating a static site into the existing Terraform project to
avoid $5-10/month of duplicate infrastructure), the AI agent loaded an outdated
`terraform.tfstate`, extracted from an archived `.zip`, as the active state file. Against that
stale state, `terraform plan` showed a long list of resources marked for *creation* — the agent's
own first anomaly signal — because the state no longer matched what actually existed in the
account.
**Root cause(s), at the system level, per the seven-step chain `cloud-security-and-guardrails-
guide` (Module 1, lesson 6) already mapped for this exact incident:**
1. **No automated validation existed between loading a state file and trusting it as ground
truth.** A stale state was accepted without any check against the account's real resources —
the first point where a systemic control, not a person's attention, could have stopped the
chain before it started.
2. **No automated, non-bypassable barrier existed between "a destructive plan is proposed" and
"it executes."** The agent *did* flag the risk (chain step 4) — an honest, correctly-functioning
signal. The chain's real failure is step 5: that signal could be approved past with a single
human decision, under normal-feeling work pressure, with no independent mechanical check behind
it. A policy-as-code gate evaluating the `plan` for delete actions on critical resources — the
exact mechanism `cloud-security-and-guardrails-guide` built for Andes Cargo as
`no-destroy-shipments.rego` (Module 4, lesson 6) — would have failed the pipeline job before
`apply` was even an option, independent of who or what proposed the change.
3. **No deletion protection existed on the database as a last line of defense.** Even if every
upstream control had failed, `deletion protection` (RDS) or `prevent_destroy` (Terraform
`lifecycle` block) would have blocked the actual delete operation at the resource level — the
last possible point of the chain, and the one `cloud-security-and-guardrails-guide` explicitly
left as a declared, unbuilt gap for Andes Cargo (Module 1, lesson 6): *"Ninguna guía de este
ecosistema la construye todavía para Andes Cargo — es, honestamente, un hueco declarado, no una
pieza escondida."*
4. **No backup mechanism existed under the team's own control.** Recovery depended entirely on an
AWS-side snapshot the team did not know existed and had not configured — not a self-service
backup the team owned and could restore on their own timeline (Module 6, lesson 6, Step 4).
None of these four root causes describes a person acting in bad faith. Per Module 7, lesson 2's
citation of Google SRE: *"You can't 'fix' people, but you can fix systems and processes."* Each of
these four is a system gap, not a character judgment — and each has a corresponding, concrete
action item (below, detailed in Module 7, lesson 4).
## What went well
- **The agent's warning was real and specific**, not a generic disclaimer — it explicitly flagged
`terraform destroy` as the proposed action before running it. The signal existed; the gap was
that a signal alone, without an independent enforcement mechanism, is information, not a
control.
- **Escalation to AWS support was fast** — the ticket opened within roughly an hour of the data
loss, with an immediate decision to upgrade to Business Support rather than wait on the standard
tier.
- **AWS's internal recovery mechanism worked, and worked completely** — all 1,943,200 rows were
confirmed intact, with no partial or silently corrupted recovery.
- **The recovery timeline was bounded and predictable once escalated** — exactly 24 hours, a
number precise enough that Grigorev could state it verbatim, which itself suggests AWS's internal
process, once engaged, followed a defined and repeatable procedure.
## What went wrong
- **No automated, non-bypassable control existed between a destructive plan being proposed and it
executing.** The single human approval at step 5 of the chain was, in practice, the entire
system's defense against this class of incident.
- **A stale state file was trusted without any validation against real infrastructure**, letting a
corrupted premise ("this infrastructure doesn't exist") propagate into a destructive action
without ever being checked against reality.
- **No deletion protection existed on the database**, so even a successful `destroy` execution had
no resource-level last line of defense.
- **Recovery depended entirely on a mechanism outside the team's knowledge and control.** The
favorable outcome was not the result of a safeguard the team had built — Module 6, lesson 6
already named this precisely: *"la recuperación de DataTalks.Club dependió de un mecanismo de
retención de snapshots del lado de AWS que ni el propio afectado sabía que existía."* A team
without that particular undocumented AWS behavior working in their favor would have faced
permanent data loss.
## Action items
Full SMART treatment — owner, priority, and verification criteria for each — is Module 7, lesson
4's subject; this section names them, without expanding them, as the direct output of the four
root causes above.
| # | Action item | Root cause it addresses |
|--:|---|---|
| 1 | Add a `prevent_destroy` lifecycle block to the `Shipments` table in `andes-cargo-infra/` | Root cause 3 — no deletion protection |
| 2 | Verify `no-destroy-shipments.rego`'s coverage extends to `terraform destroy` plans, not only `apply` plans containing delete actions | Root cause 2 — automated gate coverage |
| 3 | Write `runbooks/manifest-processor-error-rate.md`, Andes Cargo's first operational runbook | Root cause 1 and 2 — no documented, mechanical response path independent of who is on call |
| 4 | Evaluate and document DynamoDB backup/point-in-time-recovery options for `Shipments`, owned by Andes Cargo rather than depending on an undisclosed provider-side mechanism | Root cause 4 — no self-service backup |
## What this document does not do
It does not re-derive the incident's chronology, severity classification, assigned roles, or
error-budget arithmetic — all of that is `TIMELINE.md` (Module 6, lesson 8), cited by section
above, never repeated. It does not assign personal blame to the human operator or to the agent —
Module 7, lesson 2 already established, with Google SRE's own definition, why this document
instead asks what system-level gap let a good-faith decision, made with incomplete signals, cause
the damage it caused. It does not claim the four action items above are exhaustive — they are the
ones this postmortem's root-cause section directly supports; a real team's postmortem review might
surface others. It does not evaluate whether DataTalks.Club, in real life, should have had these
exact controls in place before 2026-02-26 — that is a judgment about a real company this guide has
no standing to make; the action items in this document are Andes Cargo's own response, built for
its own infrastructure.
## Consequences
Module 7, lesson 4 expands each action item into a SMART commitment with an owner and a priority.
Module 7, lesson 6 delivers action item #3 in full. Module 7, lesson 7 begins the honest work
behind action item #4. Module 7, lesson 8 packages all four alongside this document as Andes
Cargo's postmortem and runbooks deliverable.
Paso 3 — Verificando el documento
wc -l POSTMORTEM.md
grep -c '^## ' POSTMORTEM.md
grep -c '^| [0-9]' POSTMORTEM.md
Qué esperar (literal — el contenido lo ensamblaste tú, la forma es determinista):
171
10
4
Ciento setenta y una líneas, diez secciones (Summary, Impact, Detection, Resolution, Root cause(s) and trigger, What went well, What went wrong, Action items, What this document does not do, Consequences), y cuatro filas de action items — el mismo patrón determinista de verificación que TIMELINE.md ya estableció en el Módulo 6.
Paso 4 — Leyendo la sección más importante del documento: por qué tiene cuatro causas raíz, no una
Fíjate en algo deliberado de la sección "Root cause(s) and trigger": no colapsa el incidente en una sola explicación. Cuatro causas raíz, cada una en un punto distinto de la cadena de siete fallos —validación del state, gate automático antes del apply, protección a nivel de recurso, backup bajo control propio— porque un incidente de esta magnitud casi nunca tiene una sola causa que, de arreglarse, lo hubiera evitado por completo. Esto no es una elección estilística — es la misma disciplina de defensa en profundidad que cloud-security-and-guardrails-guide ya nombró en su Módulo 1, lección 6: ningún control único es "la solución completa". Un postmortem que reduce un incidente de esta escala a una sola causa raíz casi siempre está simplificando de más, y esa simplificación tiene un costo real: cada causa raíz no nombrada es una causa raíz que ningún action item va a arreglar.
Errores comunes
Escribir la sección "Root cause(s) and trigger" con el disparador y la causa raíz mezclados en un solo párrafo (de perder la distinción del Paso 1). Qué pasa: alguien escribe "la causa raíz fue el archivo .tfstate desactualizado", sin distinguir esa afirmación (correcta como disparador) de la pregunta sistémica de por qué ningún control detuvo el daño una vez que el disparador ocurrió. Cómo detectarlo: si tu postmortem tiene una sola sección de causa que no distingue "qué evento específico empezó esto" de "qué le faltaba al sistema en general". Cómo corregirlo: el documento del Paso 2 separa explícitamente Trigger (un párrafo) de Root cause(s) (una lista numerada de cuatro puntos) — cada causa raíz de la lista sigue siendo válida aunque el disparador específico (este .tfstate en particular) nunca vuelva a repetirse exactamente igual.
Tratar "Qué salió bien" como una sección decorativa u opcional, escrita solo para suavizar el tono (de subestimar su función real). Qué pasa: alguien escribe una versión mínima de "What went well" —una sola línea genérica— asumiendo que la sección importante es solo "What went wrong". Cómo detectarlo: si tu sección "What went well" no puede señalar ningún mecanismo específico que sí funcionó. Cómo corregirlo: el documento del Paso 2 identifica cuatro cosas que funcionaron —la advertencia real del agente, la escalación rápida, el mecanismo de AWS funcionando completo, un timeline de recuperación predecible— cada una con valor real: la sección no existe para hacer sentir mejor a nadie, existe para identificar qué comportamientos y mecanismos vale la pena preservar o reforzar, no solo qué hay que arreglar.
Escribir action items directamente en esta lección, en vez de dejarlos como una tabla breve que la lección 4 expande (de adelantar trabajo que rompe la secuencia del módulo). Qué pasa: alguien, al ver la tabla corta de action items del Paso 2, la reescribe de inmediato con dueño, prioridad y fecha, sin esperar a la lección 4. Cómo detectarlo: si tu versión de POSTMORTEM.md ya tiene columnas de "Owner" y "Priority" en la sección de action items. Cómo corregirlo: el documento del Paso 2, a propósito, deja esa tabla con solo dos columnas —el action item y la causa raíz que resuelve—, y dice explícitamente "full SMART treatment [...] is Module 7, lesson 4's subject". Escribir el criterio SMART completo aquí duplicaría el trabajo real de la siguiente lección sin ganar nada — la secuencia (postmortem completo primero, action items SMART después) existe para que cada documento tenga una única responsabilidad clara.
Ejercicios
Ejercicio 1 — Verifica, corriendo los comandos del Paso 3 sobre tu propia copia de POSTMORTEM.md, que obtienes exactamente 171, 10 y 4.
Ver solución
Copiando el documento del Paso 2 exactamente como aparece, wc -l POSTMORTEM.md cuenta 171 líneas totales (incluyendo líneas en blanco entre párrafos y filas de tabla), grep -c '^## ' encuentra diez encabezados de sección de nivel 2 (Summary a través de Consequences), y grep -c '^| [0-9]' encuentra las cuatro filas de la tabla de action items que empiezan con un número entre barras verticales. Si tu conteo difiere, la causa más común es un espacio en blanco agregado o quitado al copiar el bloque de código — el mismo tipo de verificación determinista que TIMELINE.md (Módulo 6, lección 8) ya estableció como estándar de esta guía.
Ejercicio 2 — Un compañero argumenta que la causa raíz #2 (ningún gate automático antes del apply) y la causa raíz #3 (ninguna protección de eliminación en el recurso) son, en el fondo, "la misma causa raíz repetida dos veces". ¿Estás de acuerdo?
Ver solución
En desacuerdo. Ambas comparten el mismo principio general (un control automático, no dependiente de la atención humana, habría detenido el daño), pero actúan en puntos distintos de la cadena, con consecuencias distintas si solo una de las dos existiera. La causa raíz #2 (un gate de política como conftest) actúa antes de que el apply siquiera sea posible — si funciona, el destroy nunca llega a ejecutarse—. La causa raíz #3 (prevent_destroy/deletion protection) actúa durante la ejecución misma, como última línea de defensa si el gate anterior, por cualquier razón (un bug en la política, una excepción mal configurada), no detuvo la acción. Un sistema que solo tuviera la causa raíz #2 resuelta seguiría expuesto si esa política específica tuviera un hueco; un sistema que solo tuviera la #3 resuelta permitiría que el plan con la acción destructiva se generara y revisara innecesariamente antes de fallar. La defensa en profundidad —el mismo principio que cloud-security-and-guardrails-guide ya nombró— exige ambas capas, no una sola, exactamente la razón por la que el documento del Paso 2 las lista como action items separados.
Ejercicio 3 — Explica por qué la sección "What this document does not do" incluye explícitamente "It does not evaluate whether DataTalks.Club, in real life, should have had these exact controls in place before 2026-02-26".
Ver solución
Sin esa declaración explícita, un lector podría interpretar las cuatro causas raíz y los cuatro action items de este documento como una crítica directa a las decisiones reales que DataTalks.Club tomó antes del incidente —una empresa real, con un fundador real, que ya pasó por un incidente genuinamente difícil—. Ese tipo de juicio retrospectivo sobre una empresa externa real excede por completo la autoridad y el propósito de este ejercicio: esta guía tiene la autoridad de decidir qué controles construye Andes Cargo, un proyecto ficticio propio, pero ninguna autoridad ni ninguna base de información suficiente para juzgar las decisiones operativas reales de una empresa externa. Declarar esta frontera explícitamente —la misma disciplina de honestidad que gobierna cada documento de esta guía— evita que el análisis de causa raíz, que debe ser riguroso, se lea como un juicio no solicitado sobre terceros reales.
Resumen y siguiente paso
En esta lección escribiste el documento central de toda esta guía: POSTMORTEM.md, con la estructura real de la plantilla de Google SRE —resumen, impacto, detección, resolución, causa raíz distinguida de disparador (cuatro causas raíz, no una), qué salió bien, qué salió mal, y una tabla breve de action items—, construido sobre TIMELINE.md sin repetir ninguno de sus hechos. Verificaste el documento con el mismo patrón determinista de todo este ecosistema: 171 líneas, 10 secciones, 4 action items. Aplicaste, punto por punto, el criterio sin culpa de la lección 2: cada causa raíz es un hueco de sistema, ninguna es un juicio sobre una persona.
Antes de avanzar deberías poder: explicar la diferencia entre disparador y causa raíz con el ejemplo concreto de este incidente; recitar las cuatro causas raíz de memoria; y defender por qué "What went well" no es una sección decorativa.
La lección 4 toma la tabla breve de action items de este documento y la expande con el criterio SMART completo — dueño, prioridad, y cómo se sabría, con evidencia, que cada uno de verdad se cumplió.
Recursos
- Google SRE Workbook — Postmortem Culture — la estructura de plantilla (resumen, impacto, detección, resolución, causa raíz, lecciones aprendidas, action items) que este documento sigue.
- Google SRE Book — Postmortem Culture — la definición sin culpa aplicada en cada sección, citada completa en la lección 2.
cloud-security-and-guardrails-guide, Módulo 1, lección 6 (06-blast-radius-revisited-what-would-have-stopped-it.md) — la fuente de la cadena de siete fallos que la sección de causa raíz mapea.- Este mismo repositorio, Módulo 6, lección 8 (
08-project-andes-cargos-final-timeline-for-this-case.md) —TIMELINE.md, la fuente citada de cada hecho de este documento. - Alexey Grigorev — How I Dropped Our Production Database — la fuente primaria de cada hecho verificado de este documento.