SKILL.md
Investigating a session recording
When a user asks "what happened in this session?" or provides a recording/session ID to investigate, gather all relevant context in parallel rather than making them ask for each piece.
Available tools
| Tool | Purpose |
|---|---|
posthog:session-recording-get |
Recording metadata (duration, counts, status) |
posthog:persons-retrieve |
Person profile (properties, distinct IDs) |
posthog:execute-sql |
Query events, errors, and page views in session |
posthog:query-error-tracking-issues-list |
Find error tracking issues linked to the session |
posthog:vision-observations-list |
Check for an existing Replay Vision AI summary |
posthog:vision-scanners-list |
Find summarizer scanners (scanner_type=summarizer) |
posthog:vision-scanners-scan-session |
Run a summarizer scanner on the session (slow, optional) |
posthog:vision-scanners-create |
Create a temporary summarizer scanner (ask first) |
posthog:vision-scanners-delete |
Delete a temporary scanner after summarizing |
Workflow
Step 1 — Get recording metadata and person profile
Start with the recording to get metadata and the person's distinct ID:
posthog:session-recording-get
{
"id": "<recording_id>"
}
The response includes distinctid, person, duration, interaction counts, console error counts, and viewing status. Use the distinctid to fetch the full person profile:
posthog:persons-retrieve
{
"id": "<person_uuid_from_recording>"
}
Step 2 — Query same-session events
Get the timeline of what the user did during the session:
posthog:execute-sql
SELECT
timestamp,
event,
properties.$current_url AS url,
properties.$browser AS browser,
properties.$os AS os,
properties.$device_type AS device_type,
properties.$screen_width AS screen_width
FROM events
WHERE $session_id = '<session_id>'
ORDER BY timestamp ASC
LIMIT 200
For sessions with many events, focus on the most informative ones:
posthog:execute-sql
SELECT
timestamp,
event,
properties.$current_url AS url,
if(event = '$exception', properties.$exception_values[1], null) AS exception_message,
if(event = '$exception', properties.$exception_types[1], null) AS exception_type
FROM events
WHERE $session_id = '<session_id>'
AND event IN ('$pageview', '$pageleave', '$autocapture', '$exception', '$rageclick')
ORDER BY timestamp ASC
LIMIT 100
Step 3 — Check for linked error tracking issues
If the recording has console errors or exceptions, find related error tracking issues:
posthog:execute-sql
SELECT DISTINCT
properties.$exception_fingerprint AS fingerprint,
properties.$exception_types[1] AS type,
properties.$exception_values[1] AS message,
count() AS occurrences
FROM events
WHERE $session_id = '<session_id>'
AND event = '$exception'
GROUP BY fingerprint, type, message
ORDER BY occurrences DESC
LIMIT 10
If fingerprints are found, search for the corresponding error tracking issues to provide links and status:
posthog:query-error-tracking-issues-list
{
"searchQuery": "<exception_type or message>"
}
Step 4 — Synthesize the investigation
Present the findings as a coherent narrative:
- Who — person properties (name, email, country, plan, etc.)
- What — sequence of pages visited and key actions taken
- Problems — exceptions, console errors, rage clicks, and their frequency
- Related issues — linked error tracking issues with their status (active/resolved)
- Context — session duration, device/browser, activity score
Optional: AI summary via Replay Vision
If the user wants a deeper analysis without reading through events manually, offer a Replay Vision summary. Follow "check-then-scan" — don't scan blindly, a scanner can only observe a given session once.
- Check for an existing summary. A scheduled scanner may already have one:
``json posthog:vision-observations-list { "sessionid": "<sessionid>" } ``
Look for an observation where scannersnapshot.scannertype is summarizer and status is succeeded. If found, read scannerresult.modeloutput (title, summary, intent, outcome, friction_points, keywords) — done, no new scan needed.
- Find a summarizer scanner if none exists yet:
``json posthog:vision-scanners-list { "scanner_type": "summarizer" } ``
- Exactly one → use it. - More than one → show the user the scanners (name + prompt) and ask which to use. - None → no summarizer scanner exists. See No summarizer scanner? Run a temporary one below.
- Scan the session with the chosen scanner. Warn this is async and takes
several minutes (rasterize + LLM):
``json posthog:vision-scanners-scan-session { "id": "<scannerid>", "sessionid": "<session_id>" } ``
- Retrieve the result by polling
vision-observations-list(step 1) until
the new observation reaches succeeded.
No summarizer scanner? Run a temporary one
If the project has no summarizer scanner, you can still produce a one-off summary with a throwaway scanner — but ask the user's permission before creating anything.
- Ask permission to create a temporary summarizer scanner just to summarize
this one session.
- Create it disabled so it never sweeps on a schedule — a disabled scanner
only runs when you trigger it on demand, so it won't touch other sessions or burn quota in the background:
``json posthog:vision-scanners-create { "name": "Temporary on-demand summary", "scannertype": "summarizer", "scannerconfig": { "prompt": "Summarize what the user was trying to do, whether they succeeded, and any friction they hit." }, "query": { "kind": "RecordingsQuery" }, "model": "gemini-3-flash-preview", "enabled": false } ``
- Scan this session on demand with the new scanner, then poll for the result:
``json posthog:vision-scanners-scan-session { "id": "<newscannerid>", "sessionid": "<sessionid>" } ``
Poll vision-observations-list until the observation reaches succeeded and read scannerresult.modeloutput.
- Ask whether to keep or delete the scanner. Once you have the observation,
ask the user if they want to keep the temporary scanner or delete it with vision-scanners-delete. Deleting is safe: the summary you just read is also emitted as an event that persists after the scanner is gone, so cleaning up the temporary scanner does not lose the result.
Tips
- Run steps 1-3 in parallel when possible — they're independent queries.
- If the recording has very few events, the session was likely very short.
Note this rather than suggesting something is broken.
- Console error count from the recording metadata is a good signal for whether
to dig into exceptions. If it's 0, skip step 3.
- The
start_urlfrom the recording tells you where the user's journey began —
use this to frame the narrative.
- If
personis null on the recording, the user was anonymous.
Person properties won't be available, but events still are.
Related skills
finding-sessions-to-watch— choose which sessions are worth investigating in the first placefinding-replay-for-issue— start from an error tracking issue and find its linked recordingsdiagnosing-missing-recordings— when a recording that should exist doesn'tcreating-replay-vision-scanners— automate this kind of watching as a scheduled scanner