skills/analysing-exported-session-recordings/SKILL.md
Decode and analyse a downloaded session-recording export zip (the `data/` blocks plus ClickHouse metadata) to understand what is making a recording large or slow: size composition by DOM mutations vs full snapshots vs network bodies, biggest payloads, content-type breakdown, churned tags/attributes. Use when asked "why is this recording so big?", "what's in this export?", "break down replay size for this session", "analyse the exported recording", or to validate whether a size-reduction change (e.g. skipping binary network bodies) would actually help a given session. Pairs with `exporting-session-recordings`, which produces the zip this skill reads.
npx skillsauth add posthog/ai-plugin analysing-exported-session-recordingsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
An export bundles a recording's raw v2 storage blocks (under data/) plus its
ClickHouse metadata (session-replay-events.json, events.json).
Use this skill to crack those blocks open and measure what the recording is made of,
so size or performance conclusions rest on decoded data — not on raw bytes.
Get the zip first via the exporting-session-recordings skill.
A data/ block is not readable JSON. It is two layers deep, and skipping either
layer makes the bytes look like binary garbage or "corruption" that isn't there:
decompress=False (export_recording/activities.py), so it writes the raw S3
object. Each block is snappy-compressed and written at its byte offset, zero-padded
(so a data/ file is mostly \x00 with the real block at the end). Snappy keeps
literal text runs verbatim with binary copy-tokens between them, which is why a raw
block reads as "half JSON, half binary".["windowId", {event}] rows. Events tagged cv: "2024-10" have their heavy DOM
fields (full-snapshot data; mutation adds/attributes/texts/removes)
individually gzip-compressed and stored as a binary-in-JSON string via fflate
strFromU8(gzipSync(strToU8(json)), true) — each string char is one gzip byte.Do not measure or draw conclusions from the raw block bytes. Reading them as UTF-8
produces phantom U+FFFD runs and wildly wrong size splits (e.g. network bodies look
tiny because they are still snappy-compressed). Always decode both layers first, then
measure. The decoder script below does this; reach for it before hand-rolling anything.
The exporter is lossless — it round-trips the raw S3 block (base64 through Redis, binary
write). Do not "fix" the exporter to decompress: export and import are a matched pair
that re-upload the raw blocks, so changing the export format breaks import_recording.
The decode belongs on the read side.
scripts/decode_recording_export.py does snappy-then-gzip and reports composition.
It needs a snappy codec (pip install python-snappy, or cramjam).
# size + composition report (accepts the zip directly, or an already-extracted dir)
python scripts/decode_recording_export.py export-<session_id>.zip
# also emit fully-decoded events (DOM fields un-gzipped and inlined) for deeper digging
python scripts/decode_recording_export.py export-<session_id>.zip --dump-jsonl decoded.jsonl
The report gives you:
adds/attributes/texts/removes — adds and repeated full snapshots are the
usual DOM-side offenders.style animation churn).(name, timestamp, kind, len) bodies before trusting
a total.A long single-page-app session decoded to ~70 MiB of DOM payload but ~253 MiB of
network bodies, of which binary/octet-stream (136 MiB), image/webp (18.5 MiB) and
image/svg+xml (3.5 MiB) were binary assets captured as text. The lesson that earned
this skill: measuring those same bodies on the raw (still-snappy) bytes reported only
single-digit MiB and pointed at the wrong culprit. Decoded, the conclusion flips —
skipping binary network bodies (feat(replay): skip binary/asset bodies in network capture, posthog-js #3912) is the dominant lever for a session like this, not a
rounding error. Decode before you conclude.
data-ai
Signals scout for PostHog Tasks, the agent work items a project runs. Two lenses: delivery health (runs failing, clustered by repository and error class, and retry storms) every run, and on a slower rotation demand (recurring asks across human-authored tasks that point at a product gap). Skips the scout fleet's own run rows.
devops
Signals scout for the PostHog Conversations (support inbox) product. Watches the `$conversation_*` ticket-lifecycle events for support-delivery regressions — SLA breach-rate steps, first-response latency blowouts, backlog inflow-vs-resolution imbalance, and channel / assignment concentration — and files each dated regression as a report. Complements the per-ticket product-feedback signals the emission pipeline already fires; does not re-surface individual ticket content.
development
Populates and maintains a project's data catalog (semantic layer): canonical metrics, trust marks (certifications) on warehouse tables/views, and reviewed table relationships. Use when asked to set up / seed / bootstrap the data catalog or semantic layer, to catalog a project's metrics, to certify or deprecate data sources, to propose or review table joins, or to work through the proposal review queue. To *use* an existing catalog to answer a business-number question, see querying-posthog-data instead. Trigger terms: data catalog, semantic layer, canonical metric, certify table, deprecate source, relationship proposal, metric drift, review queue.
tools
Investigate logs in a PostHog project: verify a service or deployment is healthy, explain an error spike, triage an incident, or understand what a log stream is saying. Use when the user asks to "check the logs", asks whether a service, deploy, release, or change is working or broke anything, asks why errors are up or what changed, or wants the root cause of failures visible in logs. Routes the logs MCP tools (services overview, pattern mining, before/after pattern diffing, bucketed counts, facets, raw rows) so investigations start from summaries instead of raw rows or hand-written SQL over the logs table.