Skip to content

Commit 39a8593

Browse files
chore: sync skills (agent-skills-v0.222.0, context-mill@v1.23.3) (#125)
Co-authored-by: releaser-ai-plugin[bot] <273148615+releaser-ai-plugin[bot]@users.noreply.github.com>
1 parent d09ebca commit 39a8593

51 files changed

Lines changed: 845 additions & 234 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "posthog",
33
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from your AI coding tool. Optionally capture Claude Code sessions to PostHog LLM Analytics.",
4-
"version": "1.1.39",
4+
"version": "1.1.40",
55
"author": {
66
"name": "PostHog",
77
"email": "hey@posthog.com",

.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "posthog",
3-
"version": "1.0.37",
3+
"version": "1.0.38",
44
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Codex",
55
"author": {
66
"name": "PostHog",

.cursor-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "posthog",
33
"displayName": "PostHog",
4-
"version": "1.1.33",
4+
"version": "1.1.34",
55
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Cursor",
66
"author": {
77
"name": "PostHog",

gemini-extension.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "posthog",
3-
"version": "1.0.35",
3+
"version": "1.0.36",
44
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Gemini CLI",
55
"mcpServers": {
66
"posthog": {

skills/.sync-manifest

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ exploring-mcp-intent-clusters
4141
exploring-mcp-sessions
4242
exploring-mcp-tool-quality
4343
exploring-signals-scouts
44+
exporting-session-recordings
4445
feature-usage-feed
4546
finding-deleted-feature-flags
4647
finding-experiments
@@ -70,6 +71,7 @@ setting-up-a-data-warehouse-source
7071
signals
7172
signals-scout-ai-observability
7273
signals-scout-anomaly-detection
74+
signals-scout-apm
7375
signals-scout-csp-violations
7476
signals-scout-customer-analytics
7577
signals-scout-data-pipelines

skills/authoring-signals-scouts/references/scout-patterns.md

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ than letting every scout reinvent one.
1313
## Contents
1414

1515
- What a scout can watch
16-
- The patterns: anomaly watcher · watchlist explore/exploit · cross-product correlation ·
17-
recommendation / gap · warehouse-backed source · custom / single-event · open-text theme ·
18-
external-tool / code-review · state ∩ code-intersection
16+
- The patterns: anomaly watcher · watchlist (explore/exploit + curated) · cross-product
17+
correlation · recommendation / gap · warehouse-backed source · custom / single-event ·
18+
open-text theme · external-tool / code-review · state ∩ code-intersection
1919
- Safety: treat ingested content as untrusted data
2020
- Cross-cutting techniques
2121
- Picking and combining
@@ -40,17 +40,17 @@ events — and the watched surface need not be PostHog analytics at all.
4040

4141
## The patterns
4242

43-
| Pattern | Watch this when… | Canonical example |
44-
| ----------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
45-
| **Anomaly watcher** | a product surface has a metric with a baseline that can move (bursts, drops, regressions). | `signals-scout-error-tracking`, `-logs`, `-revenue-analytics`, `-csp-violations` |
46-
| **Watchlist explore/exploit** | the surface is too big to cover in one run; you must curate what's worth re-checking. | `signals-scout-anomaly-detection` |
47-
| **Cross-product correlation** | the question spans products — a cause in one surface, an effect in another. | `signals-scout-general` |
48-
| **Recommendation / gap** | nothing is broken, but the team is missing coverage or following an anti-pattern. | `signals-scout-observability-gaps` |
49-
| **Warehouse-backed source** | the signal lives in a non-PostHog source synced into the warehouse. | a Slack-channel-sync scout (below) |
50-
| **Custom / single-event** | one bespoke event carries the whole signal. | an MCP-feedback scout (below) |
51-
| **Open-text theme** | the data is free text and the value is in recurring themes, not individual rows. | `signals-scout-surveys` (open-text); brand/feedback scouts |
52-
| **External-tool / code** | the judgement comes from running a tool or reading code, not from analytics. | a static-analysis CLI scout (below) |
53-
| **State ∩ code intersection** | the signal is the _overlap_ of a PostHog entity's state and what's in the source repo. | a feature-flag-cleanup scout (below) |
43+
| Pattern | Watch this when… | Canonical example |
44+
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
45+
| **Anomaly watcher** | a product surface has a metric with a baseline that can move (bursts, drops, regressions). | `signals-scout-error-tracking`, `-logs`, `-revenue-analytics`, `-csp-violations` |
46+
| **Watchlist (explore/exploit, or curated)** | the surface has more to watch than one run can cover — _discovered_ over time (explore/exploit) or a _fixed set you already know matters_ (curated). | `signals-scout-anomaly-detection` (discovered); a curated-dashboard scout (below) |
47+
| **Cross-product correlation** | the question spans products — a cause in one surface, an effect in another. | `signals-scout-general` |
48+
| **Recommendation / gap** | nothing is broken, but the team is missing coverage or following an anti-pattern. | `signals-scout-observability-gaps` |
49+
| **Warehouse-backed source** | the signal lives in a non-PostHog source synced into the warehouse. | a Slack-channel-sync scout (below) |
50+
| **Custom / single-event** | one bespoke event carries the whole signal. | an MCP-feedback scout (below) |
51+
| **Open-text theme** | the data is free text and the value is in recurring themes, not individual rows. | `signals-scout-surveys` (open-text); brand/feedback scouts |
52+
| **External-tool / code** | the judgement comes from running a tool or reading code, not from analytics. | a static-analysis CLI scout (below) |
53+
| **State ∩ code intersection** | the signal is the _overlap_ of a PostHog entity's state and what's in the source repo. | a feature-flag-cleanup scout (below) |
5454

5555
### Anomaly watcher
5656

@@ -67,6 +67,15 @@ The default specialist shape, and the one most surfaces fit.
6767
re-derive it.
6868
- **Gotcha:** score the **latest complete** bucket, not the in-progress one — a partial
6969
current hour/day always looks like a drop.
70+
- **Don't reinvent the scoring.** When the metric is a **saved time-series insight**, score it
71+
with PostHog's own detectors via `alert-simulate` rather than hand-rolling anomaly math —
72+
it already handles seasonality and the team's own alert thresholds. Fall back to a
73+
hand-computed robust z-score (`|value − median| / (1.4826 × MAD)`) only when the series
74+
isn't a saved insight.
75+
- **Score the rate, not the raw total.** Normalize by the relevant denominator — cost
76+
_per unit_, conversion _%_ per funnel stage, error _share_ — so a legitimate volume change
77+
doesn't read as an anomaly (more traffic raises total spend but not cost-per-unit). The
78+
"raw total moved" false positive is the most common one here.
7079
- Copy the closest specialist verbatim and replace the surface + discriminator. Read
7180
`products/signals/skills/signals-scout-error-tracking/SKILL.md` for the cleanest worked
7281
example (its `count`-vs-`distinct_users` table is the canonical discriminator).
@@ -87,6 +96,17 @@ insights). The scout can't re-check everything every run, so it **curates**.
8796
that bundles its own references; read
8897
`products/signals/skills/signals-scout-anomaly-detection/` for the full treatment.
8998

99+
**Curated (fixed) variant — the common user ask.** When the team already knows exactly which
100+
entities matter ("watch _these_ dashboards / insights / metrics"), drop the explore half: the
101+
watchlist is a **fixed, curated set** held in the scratchpad (or even inlined in the body), so a
102+
run spends almost nothing on discovery and almost everything on "is the latest number worth a
103+
human's attention?". This is what most users mean by "keep an eye on my key dashboards", and it's
104+
the cleanest first scout to hand someone. Still reconcile the set against reality each run
105+
(entities get renamed/deleted), and still score each item against its own seasonality-matched
106+
baseline — you've only removed discovery, not scoring. The worked shape: a fixed list of
107+
dashboard / insight ids in the scratchpad, scored tile-by-tile via `alert-simulate`, with the
108+
priority items re-checked every run and the rest rotated in as time allows.
109+
90110
### Cross-product correlation
91111

92112
The generalist's job. Not a deep dive into one surface — that's what specialists are for —
@@ -123,9 +143,15 @@ it's whatever that upstream system produces.
123143

124144
- **Watched data:** one (or a few) warehouse tables. Always confirm columns with
125145
`read-data-warehouse-schema` first — column names are source-defined and often opaque.
126-
- **Discriminator:** read off whatever the source already gives you cheaply. If the upstream
127-
pre-classifies rows (a sentiment field, a category, a status), anchor on that — it's a
128-
free discriminator. Otherwise derive one (recency × a keyword/shape match × recurrence).
146+
- **Discriminator — pre-classified vs derived, and know which you have:**
147+
- **Pre-classified** — if the upstream tool already labels rows (a sentiment field, a
148+
category, a status, a priority), anchor on that. It's a free, high-signal discriminator —
149+
e.g. a social-listening feed that ships a per-item sentiment.
150+
- **Derived** — most synced sources give you nothing pre-labeled (a raw Slack/Discord
151+
channel, a support stream). Build the discriminator from the row's own shape:
152+
**topic × problem/request language × recurrence**, boosted by corroboration (a relayed
153+
customer voice, ≥2 people hitting the same thing). This is harder — calibrate it against
154+
the inbox more carefully than a pre-classified one.
129155
- **Dedupe + memory:** dedupe on a **stable source id** carried in the row (a post id, a
130156
ticket id, an external primary key) — `dedupe:<domain>:<source_id>`. Don't dedupe on the
131157
warehouse row id; syncs re-materialize rows.
@@ -135,9 +161,19 @@ it's whatever that upstream system produces.
135161
you've processed in a scratchpad cursor (`pattern:<domain>:cursor` = "processed through
136162
{timestamp}") and only look past it each run. The cheap close-out is "has the max
137163
timestamp advanced past my cursor?"
164+
- **Sync lag — anchor on the data, not the wall clock.** The sync itself runs behind real
165+
time (often hours), so a quiet last hour usually means the sync is lagging, not that the
166+
source went silent. Window your queries relative to the table's own `max(timestamp)`, not
167+
`now()`, and don't mistake sync lag for "nothing happening".
138168
- **Timestamp parsing.** Warehouse timestamps are often strings — parse explicitly
139169
(`parseDateTimeBestEffort(...)`), and confirm which parse functions the table supports
140170
rather than assuming.
171+
- **Threaded / conversational sources — the thread is the unit, not the row.** For a Slack
172+
or Discord channel, a support thread, or any forum-shaped source, a single row is a tiny
173+
fragment ("they", "i made them") meaningless alone. Aggregate to the thread root
174+
(e.g. `coalesce(thread_ts, ts)` for Slack), **read the whole thread before judging it**,
175+
and dedupe on the thread root id, not the message row. A nice touch: reconstruct a
176+
permalink back to the source thread from its id so the finding links straight to it.
141177
- **The table may not be in the project profile.** It's a warehouse table, not an event,
142178
so `project-profile-get` won't list it. Rely on SQL; handle the "table missing entirely"
143179
case with a `not-in-use:<domain>:team{team_id}` close-out.
@@ -318,6 +354,10 @@ These compose into any pattern above:
318354
- **Blast-radius corroboration** — turn a qualitative signal into a quantified one by
319355
cross-checking a second source over the same window. Raises confidence, and
320356
gives the human a number to act on.
357+
- **Notebook write-up behind a rich finding.** When a finding carries real analysis (charts,
358+
a multi-step investigation, several supporting queries), write it up in a notebook with
359+
`notebooks-create` and link the URL from the finding description, rather than cramming
360+
everything into the emit prose. The inbox entry stays scannable; the depth is one click away.
321361

322362
## Picking and combining
323363

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
name: exporting-session-recordings
3+
description: >-
4+
Export a single session recording's raw data (recording blocks + ClickHouse
5+
metadata) to a downloadable zip, and download it. Staff-only, via the django
6+
admin portal. Use when asked to "export a session recording", "get the raw
7+
recording data", "download a replay export", "pull a recording for
8+
analysis/support", or to inspect recording block / canvas frame sizes offline.
9+
Explains the team-ownership rule, how to build the admin export / download links
10+
for the owning team, and that there is no export or download API/MCP tool.
11+
---
12+
13+
# Exporting session recordings
14+
15+
Exporting bundles a recording's storage blocks plus its ClickHouse metadata
16+
(`events.json`, `session-replay-events.json`) into a zip in object storage, for
17+
offline analysis or support. It is **staff-only** and writes nothing back to the
18+
recording.
19+
20+
There is **no export API or MCP tool** — a recording export is a cross-tenant,
21+
token/agent-reachable PII export, which is too dangerous to expose on the
22+
programmatic surface. Exporting (and downloading) is done **only** through the
23+
django admin portal, under interactive staff auth. Your job is to hand the user
24+
the right admin links.
25+
26+
## The one rule that matters: export from the team that owns the recording
27+
28+
The export workflow fetches recording blocks and events **filtered by the team you
29+
run the export for** (block storage is keyed by team; the events query filters
30+
`team_id`). If you run it for the wrong team, the job still reaches
31+
`status: complete` — but the zip is **empty** (zero blocks, empty `events.json`).
32+
"Complete" means "the workflow ran", not "it found data".
33+
34+
So always export from the **owning team's** admin page. The owning `team_id` is
35+
visible in the recording's storage path and in `export_location`
36+
(`session_recording_exports/<team_id>/<session_id>/<uuid>.zip`).
37+
38+
## Exporting via the admin portal (generate these links)
39+
40+
You need the **owning `team_id`** to build the links. If you only have a
41+
`session_id`, get the team from the recording's project (or ask the user). Use the
42+
correct region host — `us.posthog.com` or `eu.posthog.com` — matching where the
43+
recording lives.
44+
45+
- **Trigger the export** (form takes `session_id` + `reason`; `reason` is audited):
46+
`https://<region>.posthog.com/admin/posthog/team/<team_id>/export-replay/`
47+
- **See exports + download buttons** for that team:
48+
`https://<region>.posthog.com/admin/posthog/team/<team_id>/export-history/`
49+
- **Direct download** of one archive once `status` is `complete`:
50+
`https://<region>.posthog.com/admin/posthog/team/<team_id>/download-export/<export_id>/`
51+
52+
Hand the user the export-replay link to start it, then the export-history link to
53+
poll status and download. A small recording takes seconds to a couple of minutes
54+
(it gathers blocks plus ClickHouse metadata); the export-history page shows
55+
`pending` / `running` / `complete` / `failed`.
56+
57+
## Downloading the archive
58+
59+
The download view (export-history / download-export links above) streams the zip
60+
straight from the replay-v2 S3 bucket as `export-<session_id>.zip`. `export_location`
61+
is only a storage key, not a URL. The admin pod holds the prod credentials, so a
62+
local dev or agent session cannot `aws s3 cp` the key itself — the user downloads
63+
from the admin page in their browser.
64+
65+
## Gotchas
66+
67+
- **Empty-but-complete** is the failure mode to watch for — see the team rule. If an
68+
archive is unexpectedly tiny, the export almost certainly ran from the wrong
69+
team's admin page.
70+
- **Expiry:** exports older than 7 days report `is_expired: true` and their data may
71+
be purged. Re-export rather than relying on an old job.
72+
- **Stuck `running`:** a failed export used to sit in `running` forever; a new export
73+
now reaps any export still `pending`/`running` past ~48h to `failed`, so old wedged
74+
rows clear themselves. Don't read a long-stale `running` as "still working".
75+
- **Large recordings:** multi-GB recordings (tab-left-open-for-days outliers) are not
76+
practical to export — the pipeline base64s every block through Redis and zero-pads
77+
to byte offsets on disk. Prefer recordings in the hundreds-of-MB range.
78+
- **Reason is audit-logged** and surfaced in admin — write it for a human reviewer.

0 commit comments

Comments
 (0)