During an incident, memory is unreliable and the clock is not. A timeline — who was paged, what was observed, what was tried, when the change landed — is what makes a response recordable and later reviewable.

Write as it happens

Memory is the least reliable instrument in an incident, so record while the alert is fresh. Capture the timestamp of the first alert, the initial symptoms, each action tried and its observable result, and the moment the fix actually took effect. That running record is what keeps a team from repeating a failed fix during the response, and what later makes an honest postmortem possible instead of a plausible story. Make the timeline a live document from the minute the page fires: note the tool that alerted, the metric, the suspected and the confirmed cause, and record ‘same as before, did not help’ rather than silently re-trying.

Separate communication from execution

A responder who is both debugging and narrating usually does both badly. Split the work: one person acts, another maintains the timeline and keeps anyone listening updated on status, scope, and open questions. That second seat prevents the classic failure where everyone is heads-down troubleshooting and nobody is recording what was tried. A shared timeline also shortens onboarding: a teammate who arrives late reads the record and catches up in seconds, instead of interrupting the lead with questions or trusting a scattershot retelling. Update it as events land; a dropped arrow is later an hour of confusion.

Learning requires the record

A postmortem written from memory is a plausible story; one written from a timeline is a reconstruction of facts you can cross-check. Because the timeline pins actions to timestamps and outcomes, the review stops being a blame exercise and becomes a search for the systemic gap: which alert did not exist, which runbook was wrong, which decision had no owner. That is the real payoff of recording from the first minute. The whole process, the live record, the handoff, and the retrospective, only works if the habit starts at the very first alert, before anyone knows how the story ends.

When it breaks

A live timeline fails while the incident is still open, not after it. The first failure is a clock without a zone: one responder writes 10:02 from a laptop on local time while the alert pipeline logs in UTC, so two identical marks sit thirty minutes apart. Without a date and timezone, sorting is guesswork and cause and effect cannot be trusted.

The second failure is the next-morning rewrite. The team reconstructs the record from memory, keeps the fix, drops the two retries that did not help, and reorders events into a tidy story. That version hides exactly what the next responder needs: what was already tried and what observably changed.

The third failure is parallel editing with no sequence. Two responders append at once, both claim the rollback slot, and the shared doc ends with duplicate entries or silently merged lines. A late joiner cannot tell which change landed first.

The fourth failure is the chat burial. The failover decision is approved deep in a thread with two hundred side messages, but nobody copies the decision, owner, and expected effect into the timeline. An hour later nobody can answer who decided what. Unlike the companion postmortem piece about blame-free learning after the fact, these are recording failures during the response itself.

Check it quickly

Save the following as timeline_check.py and run python3 timeline_check.py. It uses only the Python standard library, builds one good and one bad in-memory timeline, writes no files, and checks the three fields every live entry needs: t, actor, and action.

"""Live incident timeline validator: each event needs t/actor/action, ordered by time."""
from datetime import datetime


def parse_time(raw):
    text = str(raw or "").strip().replace("Z", "+00:00")
    # Require a full timestamp with date and timezone, not a bare wall-clock time.
    if "T" not in text or not text:
        raise ValueError(f"bad timestamp {raw!r}: use ISO-8601 with date and timezone")
    try:
        stamp = datetime.fromisoformat(text)
    except ValueError:
        raise ValueError(f"bad timestamp {raw!r}: use ISO-8601 with date and timezone") from None
    if stamp.tzinfo is None:
        raise ValueError(f"bad timestamp {raw!r}: missing timezone")
    return stamp


def check_timeline(events):
    errors = []
    seen = []
    for i, ev in enumerate(events):
        for field in ("t", "actor", "action"):
            if not str(ev.get(field, "")).strip():
                errors.append(f"event {i} missing {field}")
        try:
            seen.append(parse_time(ev.get("t", "")))
        except ValueError as exc:
            errors.append(f"event {i} {exc}")
            seen.append(None)
    stamps = [s for s in seen if s is not None]
    # Only judge ordering over entries that parsed, so one bad clock
    # does not hide a second sequencing mistake.
    ordered = sorted(stamps)
    if stamps != ordered:
        errors.append("events out of order: sort by t before appending")
    return errors


GOOD = [
    {"t": "2027-03-01T10:01:00+00:00", "actor": "pager", "action": "alert fired: error-rate burn"},
    {"t": "2027-03-01T10:04:00+00:00", "actor": "ana", "action": "took timeline keeper seat, opened shared doc"},
    {"t": "2027-03-01T10:09:00+00:00", "actor": "lee", "action": "rolled back deploy 1a2b3c, error-rate still 8%"},
    {"t": "2027-03-01T10:14:00+00:00", "actor": "ana", "action": "decision: fail over to region-b, owner lee, effect visible by 10:20"},
]

BAD = [
    {"t": "10:09", "actor": "lee", "action": "rollback done?"},
    {"t": "2027-03-01T10:04:00+00:00", "actor": "", "action": "someone joined late, backfilled from memory"},
    {"t": "2027-03-01T10:02:00+00:00", "actor": "ana", "action": ""},
]


def main():
    good_errors = check_timeline(GOOD)
    bad_errors = check_timeline(BAD)
    assert good_errors == [], f"good sample should pass, got: {good_errors}"
    assert any("missing actor" in e for e in bad_errors), f"bad sample should flag actor, got: {bad_errors}"
    assert any("missing action" in e for e in bad_errors), f"bad sample should flag action, got: {bad_errors}"
    assert any("bad timestamp" in e or "timezone" in e for e in bad_errors), f"bad sample should flag clock, got: {bad_errors}"
    assert any("out of order" in e for e in bad_errors), f"bad sample should flag order, got: {bad_errors}"
    print("PASS: good timeline has t/actor/action on every event in time order")
    print(f"FAIL as expected: bad timeline -> {'; '.join(bad_errors)}")
    print("summary: 1 passed check, 1 failed check (expected 1 and 1)")


if __name__ == "__main__":
    main()

The good sample passes because every event carries a full ISO-8601 timestamp with timezone, a named actor, and an observable action in time order. The bad sample fails together on a bare wall-clock time, a missing actor, a missing action, and backward stamps. Expect one PASS line, one FAIL line naming each defect, and a one-passed-one-failed summary. The script was verified with Python 3.11 on macOS. It checks recording discipline during the response, not the post-incident review in the companion postmortem article.

Further reading

Sources