---
name: usability-test-issue-tickets
description: "Turn a folder of usability test recordings into draft Linear issues with a short clip each; Vivu finds where testers said they were confused or hit an error. Use after a test round."
---Usability test recordings to issue tickets with Vivu
This skill turns a round of recorded usability sessions into issues the design team can act on. The user hands over a folder of session recordings (exports from UserTesting, Maze or Lookback, or Zoom and Loom recordings). Claude indexes them in Vivu, searches for the moments where a tester says out loud that they are confused and the moments where an error dialog appears on screen, checks each moment against the recording, looks at the pointer around each one for signs of hesitation, groups the moments into issues by screen, cuts a short clip for each, and, with the user's approval, files the issues in Linear and posts one summary to Slack.
The value is in what happens on screen while the tester talks, or while they say nothing. An error dialog like "Payment method declined" often goes unmentioned, especially in unmoderated tests where many testers stay silent, so a transcript never shows it. A remark like "where do I put the promo code?" is in the transcript, but the designer needs to see which page the tester was on and what they were looking at, and the clip is what goes into the ticket. Vivu finds both kinds of moment; Claude checks every one in the recording before it becomes part of an issue.
When to use
Use when someone asks to "go through these usability test recordings and file the problems", "turn our user testing videos into Linear tickets", "find where testers got stuck", or wants a summary of a test round for designers without watching every session. For a single question about one session ("what did tester 3 say about pricing?"), search Vivu directly instead. For a pile of dozens of hours of old sessions, pick one round or one task first (Step 2 explains why). If the goal is to find testers who hesitated silently without speaking or hitting an error, this skill cannot do that reliably; Step 6 explains the limit.
Working principles
- Report measured numbers, not estimates. When a number is an estimate, say so.
- Nothing is "verified" until it has been checked against the source video. A search result is a candidate until Claude has looked at frames or read the transcript for that moment.
- Stop and tell the user when a required capability or tool is missing. Do not guess around it.
- Ask the user before anything that is expensive to redo or that acts on their behalf: indexing (it uses plan minutes), creating Linear issues, and posting to Slack.
- Hesitation seen on the pointer is a note on an issue, never an issue on its own. Vivu's search for pointer movement was not reliable enough in our test run to report from (Step 6).
- Testers are people who agreed to be recorded for research. Their names stay out of issues and messages; sessions are referred to by file name.
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 project, open its upload page, search, read section summaries | 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; ask the user to reconnect Vivu and allow write access |
| The session recordings as local video files | clips and frames are cut from the originals; the Vivu connector has no export tool | the user gives the folder; ffprobe reads each file in Step 2 |
| A transcript for each session with speech | quotes in issues come from a transcript, because Vivu's reason text paraphrases | the testing platform's transcript export, or a local speech to text tool; open one file |
| A shell with ffmpeg and ffprobe on the user's computer (Claude Code in the terminal or the Code tab of Claude Desktop) | durations, frames, contact sheets, clips | ffmpeg -version and ffprobe -version |
| An upload path | moving the recordings into Vivu | vivu_open_upload_page plus the user's own browser, or a browser tool that can attach local files |
| Linear connector, only if issues go to Linear | Step 8 creates the issues | list the teams once and confirm the target team is there |
| Slack connector, only if a summary goes to Slack | Step 8 posts one message | read the target channel once |
This skill runs once per test round. It needs no scheduler and no residential IP, because it downloads nothing from public sites. Without Linear or Slack it still produces the issue list, the clips and a draft message in the working folder.
Inputs to collect
Ask for anything missing, most important first.
- The folder of session recordings. Required. Unmoderated platforms let the study owner download each session as an MP4; if downloads are off, ask the study owner.
- The transcripts, if the platform exports them. Default: sessions without a transcript are still searched for error dialogs, but a confused remark in them is reported only with a paraphrase marked NOT VERIFIED.
- What the round tested (the tasks or flows). Default: read the task list from the platform export if there is one; otherwise ask. It names the screens when grouping issues.
- Where the issues go. Default: issues.csv and clips in the working folder. Linear only when the user names the team; Slack only when the user names the channel.
- The Vivu project. Default: a new private project named "Usability ROUND" (ROUND is the round's name or date). Session recordings show testers' voices, faces and screens, and vivu_create_project makes a project visible to the whole organization unless visibility is private.
Files and state
Keep everything in one working folder on the user's computer:
usability-round/
config.json round name, tasks, destinations, Vivu project id
state.json per session: video_id, status; search job ids; checked moment keys; issues filed
sessions/ the original recordings, videos only, no subfolders
transcripts/ one transcript per session, named like the recording
results/ each search result saved exactly as returned
frames/ triage frames and contact sheets
moments.csv one row per checked moment
issues.csv one row per issue
clips/ one clip per moment
slack_message.txt the summary message, before posting
state.json is written after every step. A rerun reads it and skips finished work: sessions already ready are not uploaded again, searches with a saved result file are not rerun, and a moment key (session file name plus the second it starts, such as checkout_s04.mp4@25) that is already in state.json is not added twice. Issues already filed keep their Linear ID. Run every command below from inside usability-round/, because the paths are relative to it.
Step 1: Check the Vivu connector and the setup
Goal: confirm every row of What you need before touching the recordings.
- 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 it returns an account with can_create_projects: true, continue. If a later write call fails with "has not granted vivu.write", ask the user to reconnect Vivu and allow write access.
- Run ffmpeg -version and ffprobe -version. If either is missing, stop and ask the user to install ffmpeg.
- If the user wants Linear or Slack, list the Linear teams or read the Slack channel once to confirm access.
Done when the user has seen a short checklist with every requirement marked present or missing, and nothing required is missing.
Step 2: Measure the sessions and price the round
Goal: the user approves the index minutes and search credits before anything is uploaded.
A backlog of dozens of hours of sessions is far more than a monthly plan indexes, and old rounds test screens that have since changed. So this skill indexes one round (or one task across rounds) at a time.
- Copy the recordings into sessions/ and measure each one:
ffprobe -v error -show_entries format=duration -of csv=p=0 sessions/FILE
FILE is the recording's file name including its extension, such as checkout_s04.mp4. Run it once per recording. Sum the seconds and divide by 60: that is the index minutes this round uses. 2. Call vivu_get_usage for the user's plan and what remains this month. 3. Work out search credits. Each round runs the two precise searches in Step 4 once, however many sessions it holds. A precise search uses 5 credits and a fast search uses 1 credit (this skill runs no fast searches), so a round is about 10 credits (estimate; add 5 for every search rerun). 4. Show one table and wait for a yes:
| This round | Remaining this month | Free plan | Premium plan ($30 a month) | |
|---|---|---|---|---|
| Index minutes | sum of the ffprobe durations | from vivu_get_usage | 20 a month | 180 a month |
| Search credits | estimate from item 3 | from vivu_get_usage | 50 a month | 500 a month |
If the round does not fit, offer these levers in order: index only the sessions for the task the team is deciding on now, then split the round across two months, then a larger plan. Do not trim sessions to save minutes; the stuck moment is often near the end of a task.
Done when the user has approved the round, and config.json lists the sessions and their durations.
Step 3: Create the project and upload
Goal: every session in the round shows ready in Vivu.
Before this step, ask the user to confirm that the testers agreed to their recordings being analyzed with a third party tool (see Compliance).
- Call vivu_list_projects and reuse the round's project if one exists. Otherwise call vivu_create_project with name "Usability ROUND" and visibility "private". Record the project ID in config.json.
- Call vivu_open_upload_page with the project ID. It returns upload_url, a one time sign in link that expires in 180 seconds, so request it only when the user is ready, hand it over at once, and never store or post it.
- The user opens upload_url in their own browser and picks the files from sessions/. If a browser tool that can attach local files is available, it can do this instead; Claude in Chrome accepts at most 10 MB per upload call, and most session recordings are larger, so those go in through the user's browser or the Vivu web app. Never split or recompress a recording to make it fit.
- Vivu replaces spaces and brackets in file names with underscores. Match Vivu's file names back to sessions/ after making the same replacement.
- Poll vivu_list_videos about every 30 seconds until every video shows ready. Record each video_id in state.json.
In our test run the files were uploaded without a browser, so the browser upload itself was not exercised in our test run.
Done when every session in config.json has a video_id in state.json and vivu_list_videos shows it ready.
Step 4: Run the two searches
Goal: candidate time ranges for confused remarks and error dialogs, saved as returned.
| Field | Query | Mode | maximum_results |
|---|---|---|---|
| said_confused (main output) | the tester says out loud that they are not sure what something means or asks where something is | precise | 20 |
| error_shown (main output) | an error message pops up in a dialog box on the screen | precise | 20 |
Why these two, in this order: the first finds the problems testers put into words, with a time to look at. The second flips from what was said to what was shown, and catches the errors nobody mentioned. Together they cover talking testers and silent ones, as long as something went visibly wrong or someone spoke up. Both are precise, because only precise returns time ranges; fast returns whole files and is for picking files in a large project, which one round does not need.
maximum_results is also the ceiling on how many ranges come back. A round of five to ten sessions rarely has 20 confused remarks or 20 error dialogs; if either search returns exactly 20, the ceiling was hit, so rerun it per group of sessions or with a higher value (the tool accepts 1 to 100).
Run each query with vivu_search_videos (project_id, query, mode, maximum_results). 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 returned in results/FIELD.json. Show the Vivu result page link in the reply; it expires after four hours, so it never goes into issues.csv, moments.csv, state.json, Linear or Slack.
If the team's product uses words other than "error" for failures (a toast, a banner, a red inline message under a field), add a third query in their words and mark it as a candidate query: it was not exercised in our test run.
Done when both result files are saved and the user has seen the ranges listed per session, marked as candidates.
Step 5: Check every moment
Goal: every range confirmed or rejected against the recording, with the false positives counted.
- Error ranges. Extract three frames (start, middle, end) and look at each one:
ffmpeg -v error -ss SECONDS -i sessions/FILE -frames:v 1 -q:v 3 frames/FIELD_hN_mid.png
SECONDS is a time in the range, FIELD the query field, N the result number. A range where no frame shows an error dialog is a false positive. Read the dialog title and message from the frame, not from the reason text. The first frame of a range often shows the page before the dialog appears, which is useful: it shows what the tester did just before. 2. Confused remarks. Read the transcript for the range and find the sentence. Then call vivu_get_video_summary with project_id, video_id, include_segments: true, and start_ms and end_ms around the range, as a second signal. The transcript decides: a section summary can leave a remark out, and the reason text paraphrases, so neither is quoted. A range with no confused sentence in the transcript is a false positive. Without a transcript, keep the moment as a candidate marked NOT VERIFIED. 3. One range can hold two remarks or two dialogs. Record each one as its own moment. 4. Write moments.csv, one row per checked moment:
moment_key,session_file,start_s,end_s,signal,verified_by,frame_files,note
checkout_s04.mp4@25,checkout_s04.mp4,25.1,28.6,said_confused,transcript,frames/sheet_said_confused_h3_02.png,"Checkout: cannot find where to enter a promo code"
- Count false positives per search and show them to the user with the confirmed moments. If fewer moments are confirmed than the user expected, report the real number.
Worked example from our test run
We used five synthetic session recordings (the longest 90 seconds, the shortest 46 seconds, 4.96 minutes in total), built with a script that logs the true time of every remark, dialog and pointer movement. Two sessions had think aloud narration and three had none. The narration never mentioned the error dialogs, and no confused wording appeared on screen, so each search could only succeed through its own signal. Look alikes were planted: neutral remarks ("I will call it Test Lab"), success dialogs, a neutral confirmation dialog and a dropdown menu.
Three of the sessions were indexed the day before; the two new ones used 2.47 index minutes and were ready about 1.3 minutes after the upload started. The two searches used 10 search credits (estimate).
The said_confused search returned 4 ranges: 4 real, 0 false, 0 missed against the five remarks we scripted. One 22 second range held two remarks and its reason named only one of them. The ranges had a median width of 10 seconds. None of the neutral remarks came back, and nothing came back from the sessions without narration.
The error_shown search returned 5 ranges: 5 real, 0 false, 0 missed. The reason text read each dialog title correctly. None of the success dialogs, the confirmation dialog or the dropdown came back. The session with no narration at all, which isolates the screen signal, had both of its error dialogs found. The ranges had a median width of 14 seconds and began a few seconds before the dialog appeared.
In our test run the section summary for the last part of the longest session covered several pages in one section and left out one confused remark and one error dialog, which is why the transcript decides.
Done when every range is marked real or false in moments.csv, and the user has seen the counts.
Step 6: Look for hesitation around each moment
Goal: a note on each moment saying whether the pointer showed hesitation, marked as a candidate.
A tester who circles a button without clicking is stuck, but Vivu's search for pointer movement did not hold up in our test run. Across three wordings of a hesitation search, each tuned on the same test corpus, 25%, 60% and 33% of the returned ranges were false. Every false range showed a pointer resting still or making one straight move, and the reason text described circling that the frames did not show. In two of the five false ranges the tester was talking about looking for something, and the search treated those words as pointer movement. So this skill does not search for hesitation. It looks at the pointer only near moments already confirmed in Step 5, where a person can judge it.
- For each confirmed moment, build a contact sheet from 5 seconds before the range to 5 seconds after it. Each tile is one frame, half a second apart, 42 tiles per sheet:
ffmpeg -v error -ss START -t LENGTH -i sessions/FILE -vf "fps=2,scale=320:-1,tile=6x7" frames/sheet_FIELD_hN_%02d.png
START is the range start minus 5 (not below 0), LENGTH the range length plus 10, in seconds. Keep -t before -i so the sheets stop where asked. Tile k (counting from 0, left to right, top to bottom, continuing into the next sheet) is at START + k x 0.5 seconds. 2. Look for the pointer moving back and forth over one control or area for a few seconds without a click, or hovering between two options. A pointer resting still is not hesitation; a tester may be reading. 3. Write what you saw into the moment's note, starting with "candidate:", and the time span. Never open an issue from a contact sheet alone.
In our test run the sheets showed the pointer going back and forth between two plan cards after a confused remark, and circling a form field after an error dialog was closed; both matched the scripted hesitations. Hesitation far from any remark or error was not covered, because no sheet reaches it. A silent tester who never hits an error can be stuck without this skill noticing.
Done when every confirmed moment has a hesitation note (a candidate description or "none seen").
Step 7: Group into issues and cut clips
Goal: issues.csv with one row per problem and a clip for each moment.
- Group moments into issues by screen and by what went wrong. The same error on the same screen in two sessions is one issue with two moments. A remark and an error on the same screen can be one issue when they describe the same problem; say so in the title.
- Write each title as the problem a designer would fix, in plain words ("Checkout has no promo code field"), not as the tester's words.
- Cut one clip per moment, starting 5 seconds before it and lasting 15 seconds, with re-encoding so the clip starts exactly where asked:
ffmpeg -v error -ss START -to END -i sessions/FILE -c:v libx264 -c:a aac clips/ISSUE_STEM_MMSS.mp4
START is the moment's start minus 5 (not below 0), END is START plus 15. ISSUE is the issue ID (UT-1, UT-2, ...), STEM the file name without extension, MMSS the moment's start as minutes and seconds. 4. Before writing every row, show the user one row and this field mapping, and ask whether it looks right:
issue_id,title,screen,signals,sessions,moments,clip_files,hesitation_note,status
UT-1,Testers cannot tell the Team and Business plans apart,Plan picker,said_confused,1,1,clips/UT-1_onboarding_s02_0013.mp4,"candidate: pointer moves back and forth over the two plan cards before choosing",draft
| Issue field | Comes from | If unavailable |
|---|---|---|
| title | Claude's reading of the moments in the group | ask the user |
| screen | the page shown in the frames | UNCERTAIN |
| signals | which searches confirmed the moments | none |
| sessions, moments | counts from moments.csv | none |
| clip_files | Step 7 item 3 | say which clip failed |
| hesitation_note | Step 6, always a candidate | "none seen" |
The title and the grouping are Claude's judgment. If a grouping is wrong, two different problems end up in one ticket, which is why the user checks the sample first.
In our test run the confirmed moments grouped into issues by screen, two card payment errors from different sessions became one issue, and every clip came out at the requested length.
Done when issues.csv and moments.csv are written, every moment has a clip in clips/, and the user has approved the sample row and the mapping.
Step 8: File the issues in Linear and notify Slack
Goal: the issues in Linear and one summary in Slack, only if the user asked for them.
Creating issues and posting a message act as the user. Name the Linear team and the Slack channel, show one complete issue and the complete Slack message as they will appear, and wait for an explicit yes.
Issue template:
Title: TITLE
Labels: usability
Seen in N of M sessions from the usability round of DATE.
What happened: WHAT THE MOMENTS SHOW, in plain words. Quotes only from the transcript.
Pointer: HESITATION NOTE (candidate, read from a contact sheet), or leave out.
Moments:
- SESSION_FILE at MM:SS (said, checked against the transcript)
- SESSION_FILE at MM:SS (error dialog "DIALOG TITLE", checked in frames)
Clip: CLIP_FILE (attached)
Slack message:
Usability review, DATE: N sessions, K issues drafted in Linear
Said out loud
- UT-1 TITLE LINEAR_LINK
Error on screen (nobody said anything)
- UT-6 TITLE (2 sessions) LINEAR_LINK
Not covered: stuck moments where a tester neither spoke nor hit an error.
Sources in issues are session file names plus MM:SS, never Vivu result links, which expire. If the Linear connector cannot attach a local video file, say so and offer two options: create the issues without the clip and have the user drag the clips in, or keep the working folder as the deliverable. Record each created issue's ID in state.json so a rerun does not file it twice.
This step was not exercised in our test run: the issue and the message were rendered to files and not sent.
Done when the user has approved the sample issue and message and they exist in Linear and Slack, or the user has chosen the local files instead.
Compliance
- Consent of the people recorded. Testers agreed to be recorded for research, but not always to their recording being uploaded to a third party analysis tool. Before Step 3, ask the user to confirm the consent covers this; if unsure, ask the research lead first.
- Minors. If any tester is under 18, stop and ask whether a guardian consented to this use; without that, do not index those sessions.
- Personal data on screen. Sessions show testers' names, emails and sometimes payment details typed into forms. Clips attached to Linear are visible to everyone on that Linear team; before Step 8, look at each clip's frames and tell the user which ones show personal data.
- Where the videos go. Uploading puts the recordings in a Vivu project in the user's account until they delete it. The project is private in this skill because the organization default makes it visible to every member. The skill never deletes anything on its own; if the user asks to remove the recordings afterwards, confirm first, because vivu_delete_video and vivu_delete_project are permanent.
- The skill does no face, voice or identity recognition. Issues name sessions by file name, never by the tester's name.
- Creating Linear issues and posting to Slack act as the user and need approval of a complete sample first (Step 8).
Known failure modes
| Symptom | Cause | Fix |
|---|---|---|
| (observed) one range holds two confused remarks and its reason names only one | Vivu returns a range around nearby moments, not each remark | read the transcript for the whole range and record each remark as its own moment |
| (observed) a section summary does not mention a remark the transcript has | summaries of long stretches merge several pages into one section | check remarks against the transcript; use the summary only as a second signal |
| (observed) the first frame of an error range shows the page before the dialog appears | ranges start a few seconds early | use the middle frame to confirm the dialog; use the first frame to see what the tester did just before |
| (observed) a hesitation search returned a range where the "pointer rests still while the tester asks where the promo code goes" | pointer movement search takes cues from the narration | do not search for hesitation; read the pointer on contact sheets around confirmed moments (Step 6) |
| (observed) the reason text says the pointer "moves in small circles" while the frames show it still | the reason describes motion it did not see | never report pointer movement from the reason text; only from frames |
| a silent tester was clearly stuck but no issue mentions it | neither search covers a tester who says nothing and sees no error | say this limit in the Slack summary; watch those sessions in full if the task matters |
| a search returns exactly as many ranges as maximum_results | the ceiling was hit | rerun per group of sessions or with a higher maximum_results |
| an empty result for a session that clearly has an error | visual search can miss moments; an empty result does not prove there were none | build contact sheets across the task where the error should be |
| a confused remark has no transcript to check against | the platform export had no transcript | keep it as a candidate marked NOT VERIFIED; quote nothing |
| "has not granted vivu.write" | Vivu connected read only | the user reconnects Vivu and allows 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 the user opens it |
| a file is rejected by the browser upload tool | Claude in Chrome accepts at most 10 MB per upload call | the user adds that file in the Vivu web app; do not split or recompress it |
| a result's file name does not match any file in sessions/ | Vivu replaced spaces and brackets with underscores | make the same replacement in the local names before matching |