---
name: incident-war-room-timeline
description: "Turn a recorded incident bridge call into a postmortem timeline with Vivu: dashboard shares and rollback commands read from frames, spoken decisions from the transcript, in wall clock time."
---Incident timeline from a recorded war room call
This skill takes the recording of an incident bridge call (a Zoom, Google Meet or Teams meeting where people shared dashboards and terminals while they worked the incident, exported as mp4 with its transcript), the time the recording started, and the incident channel's message times, and returns a timeline table for the postmortem. Each row is one moment of three kinds: someone shared a monitoring dashboard, someone ran a rollback, failover or traffic shift command in a terminal, or the incident lead said a decision out loud. Each row has the wall clock time, the offset in the recording, the dashboard title and values or the exact command read from a full frame, the path of that frame, the words from the transcript, and an empty "for postmortem" column for the person writing it up. Claude uses the channel times to cut the recording down to the few stretches that matter, prices the indexing against the user's Vivu plan, indexes the cuts in a private Vivu project, runs one search per kind of moment, checks every hit against frames or the transcript, and converts the times to wall clock.
The value is in what only the screen share shows. A bridge call transcript says "Can everyone see my screen?" and "Okay, running it."; which dashboard was up, what the error rate read, and which command was run exist only in the picture. Channel messages are often posted minutes after the command actually ran, because people run the command first and type the message later, so the channel cannot give the time something happened. Decisions are the other way around: they are spoken on a camera grid with nothing on screen. So the skill searches the screen for dashboards and commands, searches speech for decisions, and reads every command and value from a full frame of the user's own recording before it goes into the table.
When to use
Use when an SRE, on call lead or incident commander says "build the timeline for the postmortem from the bridge recording", "when exactly did we run the rollback, the channel says 14:11 but I think it was earlier", "which dashboards did we look at during the incident", or "pull the decisions out of the war room recording". For one question about one moment ("when did she share the DB graph?"), search Vivu directly. For release demos in sprint reviews, use a release notes screenshot skill instead. This skill does not find the root cause and does not judge anyone's actions; postmortems here follow the blameless convention, and the table only records what was shown, run and said.
Working principles
- Report measured numbers, not estimates. When a number is an estimate, say so.
- Vivu's results are candidates. A row is verified only after a full frame shows the dashboard or command (screen rows) or the transcript has the decision (decision rows). Rejected candidates are counted and kept in rejected.csv with the reason.
- Stop and tell the user when a required capability or file is missing (no transcript, no recording start time, no ffmpeg, no write access in Vivu). Do not guess around it.
- Ask the user before anything that is expensive to redo or that acts on their behalf: the cut windows before upload, the indexing cost before upload, and the sample rows before the full table.
- Record what happened; never assign blame or root cause. Commands and values are copied from the frame, never paraphrased.
What you need before starting
Check each item at the start of the run and tell the user plainly what is missing.
| Requirement | Why | How to check |
|---|---|---|
| Vivu connector with write access | create a private project, open its upload page, search | vivu_get_account shows can_create_projects: true (tool names may carry a server prefix). A write call failing with "has not granted vivu.write" means the connection is read only; the user reconnects Vivu and allows write access |
| A shell with ffmpeg and ffprobe on the machine that holds the recording | cut the windows, measure minutes, extract frames | ffmpeg -version and ffprobe -version |
| The bridge recording as a local mp4 (downloaded from Zoom, Google Meet or Teams) | cuts and frames come from the local file; Vivu has no export tool | ls BRIDGE_FOLDER |
| The transcript with timestamps (the meeting tool's VTT or SRT export, or the output of a local speech to text tool) | decision rows and quotes come from it, never from Vivu's reason text | ls BRIDGE_FOLDER for a .vtt or .srt |
| The time the recording started, in UTC | every wall clock time is that start plus the offset | the meeting tool's recording list, or ask the user |
| The incident channel's messages with times (export or pasted) | to pick which stretches of a long call to index | ask the user to paste the key messages |
| A browser that can open the Vivu upload page | the connector has no direct upload tool | the user opens the link, or Claude in Chrome is connected |
The skill needs a shell and local files, so it runs in Claude Code on the user's computer (terminal or the Code tab of Claude Desktop). It downloads nothing from YouTube, so no residential IP is needed. Nothing is scheduled and nothing is written outside the working folder.
Inputs to collect
Ask for anything missing, most important first.
- The recording file and its transcript (BRIDGE_FOLDER). Required.
- The recording start time in UTC (REC_START). Required; ask if the meeting tool does not show it.
- The channel messages that mark the key moments (alert, mitigation, recovery). Required for calls longer than about 20 minutes; for a short call the whole recording can be indexed.
- Which systems were involved (service, database, region names). Default: taken from the channel messages. They help read the frames, not the searches.
- The Vivu project name. Default: "Incident INCIDENT_ID", private.
Files and state
Keep everything in one working folder next to the recording:
incident-timeline/
windows.csv cut file, CUT_START, CUT_END, minutes, the channel messages that chose it
cut/ the cut stretches of the recording, the files that get uploaded
results/ raw search results, one JSON file per search
frames/ contact sheets and full frames read for each row
incident_timeline.csv one row per verified or candidate moment
rejected.csv candidates that failed a check, with the reason
state.json uploaded files and video ids, searches run with job ids, every hit with its verdict, rows written
state.json is updated after every step. A rerun reads it first: files already uploaded are not uploaded again, searches already run are not rerun, and checked hits keep their verdicts, so an interrupted run resumes where it stopped.
Step 1: Check the Vivu connector and the setup
Goal: every row of What you need is confirmed before anything is cut or uploaded.
- Call vivu_get_account. If the tool is missing, tell the user to add the Vivu connector in Claude (https://mcp.vivu.ai/mcp) and stop. If can_create_projects is not true, or a write call later fails with "has not granted vivu.write", ask the user to reconnect Vivu with write access and stop until they have.
- Run ffmpeg -version and ffprobe -version. List BRIDGE_FOLDER and check that the recording has a transcript.
- Create the working folders; ffmpeg does not create an output folder:
mkdir -p incident-timeline/cut incident-timeline/results incident-timeline/frames
Run every later command from inside incident-timeline/ (cd incident-timeline).
Done when vivu_get_account shows can_create_projects: true, ffmpeg and ffprobe print versions, and the working folders exist.
Step 2: Confirm consent and collect the inputs
Goal: the user has confirmed the recording may be used and Claude has REC_START and the channel messages.
- Ask Compliance items 1 and 2 before anything else in this step. Stop if the recording fails them.
- Get REC_START. Check it against one known moment: a channel message like "bridge is up" should land a little after the recording's first speech, never before it.
- Collect the channel messages with their times.
In our test run the recordings were made for the test and their start times were set in advance, so this step was not exercised in our test run.
Done when the user has confirmed consent and REC_START, and the channel messages are in windows.csv notes.
Step 3: Pick the windows from the channel and cut
Goal: a few cut files that cover the moments the timeline needs, so index minutes go only where they count.
- Group the channel messages into stretches (alert and first look, mitigation, recovery). For each, set the window from about five minutes before the first message to a few minutes after the last. Channel messages lag the screen, so the margin goes mostly before the message. Aim for 3 or 4 windows of 10 to 15 minutes; merge windows that overlap.
- Convert each window to seconds in the recording (message time minus REC_START), show the windows to the user and let them adjust.
- Cut with re-encoding, so the cut starts exactly at CUT_START and every time Vivu returns maps back by adding CUT_START (RECORDING_FILE is the original file; N is the window number):
ffmpeg -v error -ss CUT_START -to CUT_END -i RECORDING_FILE -c:v libx264 -c:a aac cut/window_N.mp4
A stream copy (-c copy) is faster but starts at the nearest keyframe, a few seconds early, and every time in the table is then off by an unknown amount.
In our test run both narrated recordings were cut this way from a simulated channel log, and the times in the table were mapped back by adding CUT_START.
Done when cut/ holds one file per window and windows.csv records each CUT_START.
Step 4: Price the indexing and get approval
Goal: the user sees the cost before anything is uploaded.
- Measure each cut file and sum the minutes. ffprobe takes one input file per call:
ffprobe -v error -show_entries format=duration -of csv=p=0 cut/window_N.mp4
- Call vivu_get_usage for the plan and what remains this month.
- Search credits: three precise searches (dashboard, command, decision), 5 credits each, plus 5 for each rewording if a first page is not clean. Label the total as an estimate.
- Show one table and wait for approval:
| This incident | Remaining on the plan | |
|---|---|---|
| Index minutes | measured sum | from vivu_get_usage |
| Search credits | estimate | from vivu_get_usage |
For reference, the Free plan has 20 index minutes a month and 50 search credits a month; Premium is $30 a month with 180 index minutes and 500 search credits. As an estimate, a whole 90 minute bridge call is half of Premium and does not fit Free, while four 12 minute windows are about 48 minutes, and Free has room for one window. If the windows still do not fit, offer these levers in order: tighten the windows around the channel messages, drop the window the user cares least about (say which), and only then a larger plan.
In our test run the account was an admin account whose vivu_get_usage shows no remaining allowance, so the comparison against a real plan and the approval were not exercised in our test run.
Done when the user has approved the minutes and the credit estimate.
Step 5: Upload and index
Goal: every cut file is ready in a private Vivu project.
- Call vivu_list_projects and reuse the project named in the inputs if it exists. Otherwise call vivu_create_project with that name and visibility "private". Bridge calls show production systems, internal hostnames and sometimes customer data or secrets; the default visibility is the whole organization.
- Call vivu_open_upload_page with the project ID right before the upload. The link expires in 180 seconds and is a sign in link, so never paste it into a message or file. Give it to the user to open in their own browser and choose the files in cut/, or attach them with a browser tool that can upload local files. Claude in Chrome accepts at most 10 MB per upload call; larger files the user adds in the Vivu web app. Never split or recompress a recording to fit.
- Poll vivu_list_videos about every 30 seconds until every file shows ready. Match each video back to windows.csv by file name; Vivu turns spaces and punctuation in names into "_". Record the video IDs in state.json.
In our test run the files went up through a script, not through a user's browser or Claude in Chrome; that upload path was not exercised in our test run.
Done when vivu_list_videos shows every file in windows.csv as ready.
Step 6: Search the three kinds of moments
Goal: one raw result file per kind of moment.
Run each query with vivu_search_videos (project_id, query, mode "precise", maximum_results 15). It returns a job ID. Call vivu_get_search_results until complete is true; each status call can wait up to 45 seconds, so a pending search is not a stalled one. Save each completed result as results/FIELD.json. Show the result page link in the live reply only; it expires after four hours, so it never goes into the table or any file.
| Field | Query | Mode | maximum_results |
|---|---|---|---|
| dashboard | someone shares their screen showing a monitoring dashboard with graphs of error rate, latency or connections over time | precise | 15 |
| command | someone types a rollback, failover or traffic shift command into a terminal on the shared screen and runs it | precise | 15 |
| decision | the incident lead says out loud that the team has decided to roll back, fail over or escalate, as a decision rather than a suggestion | precise | 15 |
All three are precise because the table needs time ranges; a fast search returns only whole files, and the windows are already chosen. maximum_results 15 is more than a few windows usually hold of each kind, and it is the ceiling on what comes back: a list that stops at exactly 15 means the cap was hit, so raise it and rerun that field.
Why this chain: the dashboard search finds what the team was looking at, the command search finds what was done on screen, and the decision search flips from the screen to speech to find why, which is never on screen. The last clause of the decision query ("a decision rather than a suggestion") is there because bridge calls are full of "should we fail over?" that is not a decision.
Done when results/ holds the three result files and state.json records the job IDs.
Step 7: Check every hit
Goal: every hit has a verdict and every screen row has a value read from a full frame.
Vivu returns a time range that contains the moment, not the exact second, and the reason text is a paraphrase. In our test run the reason quoted every command correctly, but it also wrote "roll check out back" for a spoken "roll checkout back"; commands, values and quotes in the table come from frames and the transcript only. Reading small terminal text and dashboard numbers is the weakest part of this skill: other tests found the reason inventing small text and digits, and a contact sheet small enough to scan is too small to read a terminal.
- Dashboard and command hits: make a sheet of one frame every half second across the window (START is start_ms / 1000, DURATION is (end_ms - start_ms) / 1000; the 8x5 grid holds 20 seconds, so for a longer window raise the second number):
ffmpeg -v error -ss START -t DURATION -i cut/window_N.mp4 -vf "fps=2,scale=320:-2,tile=8x5" -frames:v 1 frames/FIELD_HIT_sheet.png
HIT is h plus the hit's number within the field (h1, h2, ...). Keep the quotes around the filter. Windows usually start and end on the neighboring screen, so find the first cell that shows the dashboard, or for a command the first cell where the command's output appears, and use that cell's time (START plus half a second per cell before it). 2. Extract that moment as a full frame (SECONDS is the cell's time) and read it:
ffmpeg -v error -ss SECONDS -i cut/window_N.mp4 -frames:v 1 -q:v 3 frames/FIELD_HIT_read.png
For a dashboard, copy the dashboard title and the panel titles and values that matter. For a command, copy the command line exactly and its output line. When the mouse pointer covers part of the text, read a neighboring cell or the output line, and mark the field UNCERTAIN if neither settles it. Reject the hit when the frame is a document or a page that only contains a command (a runbook, a wiki, a chat message), when the command is diagnostic only (listing pods, reading logs), or when the screen is not a monitoring dashboard (a status page editor, a ticket). 3. If the frame shows a secret, a token, a password or customer data, keep the row but set flag SENSITIVE; that frame never goes into the postmortem document. 4. Decision hits: read the transcript lines inside the window. The hit is verified only if someone states a decision ("we are making the call", "decision: we roll back"), not a question or a proposal. Read vivu_get_video_summary with include_segments, start_ms and end_ms around the window as a second signal only. 5. Rejected hits stay in results/ and go into rejected.csv with the reason; they are counted, not dropped silently.
Worked example from our test run
In our test run we used 3 synthetic recordings, 4.69 minutes in total after cutting, made for the test: a meeting app frame with monitoring dashboards, terminals where commands were really typed in a small monospace font, a wiki runbook page, a status page editor and a camera grid. Each narrated recording had its own synthetic voice; the narration never named a dashboard, a metric or a command ("Can everyone see my screen?", "Okay, running it."). The third recording was a silent control with the same kinds of screens and no audio. Before searching we wrote down every target and every distractor: diagnostic commands in the same terminal, the runbook page showing the exact rollback command in a code block, a status page editor shared on screen, a speaker saying they were watching the graphs with nothing shared, and a spoken "Should we fail over to west? Maybe. Let's not decide yet." From the upload to all 3 recordings ready took 1.3 minutes.
The dashboard query returned 5 moments: 5 real, 0 false and 0 missed. The command query returned 4: 4 real, 0 false and 0 missed. The decision query returned 2: 2 real, 0 false and 0 missed. This was the first wording of each query on this corpus. None of the distractors came back, and the silent control's dashboards and commands were all found, so those hits came from the picture. Command windows were about 16 seconds wide, several times longer than the typing, and every screen window began or ended on the neighboring scene, so the sheet, not the window, gives the time. Every command and dashboard value was read correctly from a full frame; on the contact sheet only the large dashboard numbers were readable. These were clean synthetic screens; real bridge calls with smaller terminal fonts, scrolling output, several windows on one screen, cross talk and a full length call were not exercised in our test run.
Done when every hit has a verdict in state.json and the user has seen the real and rejected counts per field.
Step 8: Convert to wall clock and assemble the timeline
Goal: one table the incident lead can paste into the postmortem.
- For each verified or candidate moment: wall clock = REC_START + CUT_START + the moment's seconds in the cut file (the sheet cell for screen rows, the transcript line's start for decisions). offset_mmss is CUT_START plus those seconds, written as minutes and seconds into the full recording.
- Write the rows in time order:
wall_clock_utc,recording_file,offset_mmss,category,what_read_from_frame,frame_path,quote_from_transcript,quote_source,status,flag,for_postmortem
14:10:40,RECORDING_FILE,00:40,decision,,,"Okay. Decision: we roll checkout back to yesterday's release, right now. I will take it.",meeting transcript,verified,,
14:11:12,RECORDING_FILE,01:12,command,"kubectl rollout undo deployment/checkout-api -n prod -> deployment.apps/checkout-api rolled back",frames/command_h1_read.png,"Okay, running it.",meeting transcript,verified,,
- Field mapping:
| Field | Source | If unavailable |
|---|---|---|
| wall_clock_utc | REC_START + CUT_START + seconds in the cut | UNCERTAIN when REC_START was guessed |
| recording_file | the original file name from windows.csv | none |
| offset_mmss | CUT_START + seconds in the cut | none |
| category | dashboard, command or decision | none |
| what_read_from_frame | dashboard and panel titles and values, or the command and its output line, from the full frame | UNCERTAIN |
| frame_path | the full frame | blank for decisions |
| quote_from_transcript | the transcript lines at that moment, word for word | NOT VERIFIED |
| quote_source | meeting transcript or local speech to text | none |
| status | verified when the full frame shows the dashboard or command (screen rows) or the transcript has the decision (decision rows) | candidate when that check could not be made |
| flag | SENSITIVE when the frame shows a secret, token or customer data | blank |
| for_postmortem | left empty for the person writing the postmortem | blank |
- Show two or three sample rows and the mapping and wait for a yes before writing the full table. Say which parts are inferred: the cell chosen as "the command ran" is the first cell where its output appears, and whether a sentence is a decision is Claude's reading of the transcript.
- Write incident_timeline.csv and rejected.csv. Give the user the counts per category and, where a channel message exists for the same event, the gap between the channel time and the screen time, without comment on who posted it.
In our test run the table had a row for each checked hit; the silent recording's screen rows were verified from full frames and their quote_from_transcript was NOT VERIFIED, because it had no audio. The user's approval of the sample rows was not exercised in our test run.
Done when incident_timeline.csv exists with a row for every verified or candidate moment and the user has approved the sample rows.
Compliance
- Use only internal meetings recorded under the company's recording policy, where the people on the call knew it was recorded. Ask before Step 2 and stop if the recording does not meet this.
- Bridge calls show production systems, internal hostnames and sometimes customer data or secrets. Ask before Step 5 whether the company allows uploading this recording to a third party service, and create the Vivu project as private. The recordings stay in the user's Vivu project until the user deletes them; the skill never calls vivu_delete_video or vivu_delete_project unless the user asks, and confirms first.
- Frames that show secrets, tokens or customer data are flagged SENSITIVE and stay out of the postmortem document.
- The table records what was shown, run and said. It does not name a root cause and does not rate anyone's actions.
- No face, voice or identity recognition. Who spoke comes from the meeting tool's transcript labels or the user, not from Vivu.
Known failure modes
| Symptom | Cause | Fix |
|---|---|---|
| (observed) a command window starts on the runbook page that shows the same command in a code block | windows start a little before the moment and include the previous screen | take the time from the first sheet cell where the command's output appears, and reject any hit whose only command is in a document |
| (observed) the reason writes "roll check out back" for a spoken "roll checkout back" | the reason is a transcription with errors, not the transcript | take quotes from the transcript only |
| (observed) the terminal text is unreadable on the contact sheet | a sheet cell is too small for terminal text | read the command from a full frame at the chosen cell |
| (observed) the mouse pointer covers part of the command in the full frame | the presenter left the pointer on the line | read a neighboring cell or the output line; mark UNCERTAIN if neither settles it |
| (observed) a summary segment titled "Executing Rollback" also covers the recovery dashboard that follows | summary segments follow topics, not screens | use segments only as a second signal; rows come from frames and the transcript |
| the reason reports a value or command that the frame does not show | the reason can invent small text and digits | never copy values or commands from the reason |
| a proposal ("should we roll back?") comes back as a decision | speech searches find the topic, and a proposal is the same topic | verify every decision against the transcript and reject questions and proposals |
| an empty result for a dashboard the user remembers | an empty result does not prove it was not shared; it may be outside the windows, or a full screen chart with no title | widen the window around the channel message, rerun, and say so in the table notes |
| a list stops at exactly maximum_results | the cap cut the list short | raise maximum_results and rerun that field |
| wall clock times are off by a constant amount | REC_START is wrong, or the cut used -c copy | confirm REC_START against a known moment and cut with re-encoding |
| "has not granted vivu.write" | Vivu connected read only | the user reconnects Vivu with write access |
| upload page asks to sign in or shows an error | the one time upload link expires after 180 seconds | request a new link right before opening it |
| a file is rejected by the browser upload tool | Claude in Chrome takes at most 10 MB per upload call | the user adds that file in the Vivu web app; never split or recompress it |