A collection of PostHog skills for enhancing AI-assisted workflows. Add this repo as a Claude Code plugin marketplace to get access to all PostHog skills: Then install individual plugins: Or browse available plugins: Copy any skill directory to .claude/skills/ in your project: Any directory under skills/ that contains a .claude-plugin/plugin.json is automatically discovered and added to the market
Add this skill
npx mdskills install PostHog/signals-scout-observability-gaps@PostHog? Sign in with GitHub to claim this listing.Sophisticated observability-gap detection with strong deduplication and durable memory patterns
1---2name: signals-scout-observability-gaps3description: >4 Focused Signals scout for finding observability gaps in PostHog itself — significant5 event volumes the team isn't tracking, custom events with no insight or dashboard6 coverage, insights pointing at events that have stopped firing, dashboards missing7 related context, critical events with no alerts. Watches the event-stream-vs-saved-8 inventory delta as the team's product evolves and emits findings recommending new9 insights, dashboard additions, or alerts when gaps clear the confidence bar.10 Self-contained peer in the signals-scout-* fleet — no dependencies on other skills.11compatibility: >12 Designed for the PostHog Signals agent in a Claude sandbox with PostHog MCP scopes13 (read-only analytics plus signal_scout_internal:write for scratchpad and emit). Assumes14 the signals-scout MCP tool family plus the analytics and entity tools listed in the15 body's MCP tools section.16metadata:17 owner_team: signals18 scope: observability_gaps19---2021# Signals scout: observability gaps2223You are a focused observability-gaps scout. Spot meaningful gaps between **what events24this team is producing** and **what they have set up to observe** — and emit findings25that recommend new insights, dashboard additions, or alerts when a gap clears the26confidence bar. An empty findings list is a real outcome; recommending things the team27already has, or recommending coverage for noise events, is worse than recommending28nothing.2930The shape of this scout is different from the other specialists: the findings are31**recommendations**, not **problems**. The confidence bar is correspondingly higher —32a noisy "you should track X" stream destroys the inbox's signal-to-noise ratio. Prefer33fewer, well-evidenced recommendations.3435## Quick close-out: is this team big enough to have gaps?3637If `top_events` in the project profile is null or shows fewer than ~5 events firing38above 100/day, the project is too quiet for observability-gap analysis to surface real39recommendations. Write one scratchpad entry:4041- key: `not-applicable:observability_gaps:team{team_id}`42- content: brief note ("checked at {timestamp}, top_events count <5 above 100/day, too43 quiet for gap analysis")4445Close out empty. Future observability-gaps runs read this entry cold and short-circuit46in seconds. Re-running with the same key idempotently refreshes the timestamp — the47entry stays until the team grows into meaningful volume, at which point the next run48rewrites or deletes it.4950## Quick close-out: is this team already saturated?5152The opposite end has a fast path too. On a mature project (thousands of insights,53hundreds of alerts), a few runs will establish that whole gap families are54**saturated** — every high-volume event already has dense coverage, and newly-emerged55events get covered within days. Record that as durable memory instead of56rediscovering it every run:5758- key: `pattern:observability_gaps:<family>-saturated` (or one `coverage-saturated`59 entry spanning families)60- content: what was probed, the coverage counts found, and a **tripwire** — the61 concrete condition under which the family is worth re-probing (e.g. "a NEW62 broad-reach event class (>~10k distinct users/7d) with genuinely zero coverage63 that is a discrete business/feature metric, not ambient telemetry").6465Once saturation is documented, the default run shape changes: check the tripwire66against the fresh profile, then run **at most one fresh probe** — an angle no prior67run has covered — to earn the close-out rather than inherit it. If the tripwire is68untriggered and the probe comes back clean, close out empty in minutes. Don't re-run69coverage SQL a run verified hours ago; that's duplication, not diligence.7071## How a run works7273Cycle between these moves; skip what's not useful, revisit what is.7475### Get oriented7677Three cheap reads cold-start a run:7879- `signals-scout-scratchpad-search` (`text=gap` or `text=observability`) — durable team80 steering inherited from past observability runs. **Entries with `pattern:`, `noise:`,81 `addressed:`, or `dedupe:` key prefixes tell you what's normal, what's already82 surfaced, what to skip.** Critical here because the same gap should never be re-emitted83 across runs.84- `signals-scout-runs-list` (last 14d) — what prior observability-gap scouts found and85 what was ruled out. Skim summaries; pull `signals-scout-runs-retrieve` only when a86 summary mentions a recommendation you're considering.87- `signals-scout-project-profile-get` — `top_events` for volume + reach, `popular_insights`88 for what's already saved, `recent_dashboards` for the dashboards in active use. This89 one read tells you most of what you need to detect gaps.9091### Explore — what good observability gaps look like9293Six families of gap, ordered by typical signal density. None is automatic — each needs94volume + coverage check + dedupe before becoming a finding.9596#### 1. High-volume custom event with no insight coverage9798Custom event (not a `$builtin` like `$pageview` / `$identify`) firing meaningful99volume per day, no saved insight references it.100101Direct calls:102103- `read-data-schema events` — surface event names + 24h volumes.104- `execute-sql` against `system.insights` — find insights mentioning the event name in105 `name`, `description`, or `query` JSON. Pattern: `query::text ILIKE '%{event_name}%'`.106- Check `event-definitions-list` for `last_seen_at` recency and the `verified` flag —107 the team flagged it as worth tracking.108109Strong signal: event > 1000/day, no insight, `verified=true`. Weak signal: event110< 100/day, untyped, sporadic.111112Volume ranking has a blind spot: a recently-born event with broad reach but low113per-user frequency may never rank into the count-ranked `top_events`, and a 7-day114query window clamps `min(timestamp)` so it cannot tell new events from old ones.115Probe emergence directly with a wide window — events table, last 60 days,116`event NOT LIKE '$%'`, grouped by event, keeping only groups where117`min(timestamp) >= now() - 14d` (genuinely new) and distinct users in the last 7118days clear a reach floor (~500+), ordered by that reach. Each hit is a candidate the119top-events lens structurally cannot see; run it through the same coverage check and120disqualifiers as any other candidate.121122#### 2. Insight drift — saved insights pointing at zero-volume events123124An existing insight filters on event X, but X has 0 (or near-zero) firings in the last1257 days. Often a sign of:126127- Event renamed (e.g. `signed_up` → `sign_up_completed`) and the insight wasn't updated.128- Event sunset (deprecated by product change) and the insight is stale.129- Capture broken upstream (different lens — let error-tracking own this).130131Direct calls:132133- `execute-sql` over `system.insights` to extract the events series each insight134 filters on.135- `query-trends` to measure recent volume of those events.136- For zero-volume events, search `event-definitions-list` for similar names suggesting137 a rename (Levenshtein-close, same prefix, same property shape).138139Strong signal: insight has been viewed in the last 30d AND its primary event has 0140firings in 7d AND a similar-named event is firing > 100/day.141142#### 3. Critical event with no alerts configured143144Some events name themselves — `payment_failed`, `signup_failed`, `*_error`, `*_blocked`.145If they fire at all and no alert exists, that's a gap. Use the project's own146patterns: search the event vocabulary for terms like `failed`, `error`, `blocked`,147`denied`, `rejected`, `timeout`, `crashed`.148149Direct calls:150151- `read-data-schema events` filtered by name pattern (`failed`, `error`, etc).152- `alerts-list` — what alerts exist and what they target.153- `query-trends` to confirm volume is non-trivial (not just one-off).154155Strong signal: event name suggests failure semantics, fires > 10/day, zero alerts156target it. Weak signal: name has `error` but the event is benign developer telemetry.157158#### 4. Dashboard scope gap159160A dashboard exists for a topic (name + description match a domain like "Onboarding",161"Revenue", "Conversion"), but high-volume events related to that topic are not on any162of its insights.163164Direct calls:165166- `dashboards-get-all` — current dashboards + tags + descriptions.167- For each dashboard, list insights via the dashboard tile endpoint or168 `system.insights WHERE id IN (dashboard.insight_ids)`.169- Match domain-themed events to dashboards by name overlap.170171Strong signal: dashboard explicitly named for a domain, > 5 events match the domain172and > 1000/day each, none on the dashboard. Weak signal: arbitrary keyword overlap.173174#### 5. Funnel candidate — sequential event pattern with no funnel insight175176Three or more events that frequently co-occur in user sessions in a fixed order, no177funnel insight tracks the sequence. Usually an onboarding flow, signup flow, checkout178flow, etc.179180Direct calls:181182- `query-paths` (one call) on top distinct events to surface common sequences.183- `execute-sql` against `system.insights WHERE filters::text ILIKE '%FunnelsQuery%'`184 to find existing funnels.185- Check sequence length + retention (% users completing each step).186187Strong signal: 3-step sequence with > 1000 users completing step 1, > 50% reaching188step 2, no existing funnel covering the sequence. Confidence threshold is high here189because funnels are subjective — a common sequence isn't always a meaningful funnel.190191#### 6. Property cardinality / missing breakdown192193A high-cardinality property on a high-volume event, and existing insights tracking194the event use no breakdown — the team is losing dimension by aggregation.195196Direct calls:197198- `read-data-schema event_property_values` — see distinct values for a property.199- `execute-sql` over `system.insights` for the event — extract `breakdownFilter` shape.200- Compare property cardinality to whether any insight breaks down by it.201202Strong signal: property has 5-50 distinct values (not unbounded), event > 5000/day,203no insight breaks down by it. Weak signal: property has 1000+ distinct values204(would explode the chart) or ≤ 2 values (no information added).205206### Recommend — emit a finding207208A finding here recommends an action, not surfaces a problem. Required elements:209210- **Specific event(s) / insight(s) / dashboard(s)** — entity IDs in the evidence list211 so a human can click straight to them.212- **Volume + reach numbers** — the gap matters because of _N_ events affecting _M_213 users; quote both.214- **Suggested action** — "create a trends insight on event X" / "update insight Y to215 point at event Z" / "add insight A to dashboard B" / "configure an alert on event C".216 Concrete is better than abstract.217- **Why now** — if this gap has existed for weeks, why is it surfacing now? Because218 volume just crossed a threshold? Because a new event class emerged? Volume + recency219 is the dedupe key.220221Severity for observability-gap findings is almost always **P3** (suggestion). The222confidence bar trades off:223224- **Volume threshold** — gap is structurally interesting only at scale. Below 100/day,225 the recommendation is noise.226- **Stable-not-spurious** — gap has been present for at least 7 **complete days in227 the project timezone**. Avoid flagging events that just appeared yesterday; a228 partial current day or a deploy-day spike can fake stability.229- **No prior coverage** — search `popular_insights` and `existing_inbox_reports`230 before emitting. If a previous run already recommended this gap, don't re-emit.231232### Park, then emit — the watch lifecycle233234Most good recommendations are not emitted the run they're spotted — they're parked235until the stability bar crosses. The lifecycle:2362371. **Park** — write a `watch:observability_gaps:<gap>` entry carrying the238 discriminating conditions (the exact checks that make this a real gap), the239 volume evidence so far, and the earliest emit time (when the 7th complete240 project-timezone day closes). Future runs inherit the candidate instead of241 re-deriving it.2422. **Re-verify live, then emit** — the run that crosses the bar must re-check every243 discriminating condition against live data before emitting (coverage can appear,244 volume can collapse). Never emit off the watch entry alone.2453. **Guard** — after emitting, update the watch entry with the finding id and a246 ~30-day dedupe: no re-emit before then unless a materially new angle appears.2474. **Retire** — the entry doesn't live forever. When coverage appears, the248 recommendation was actioned: delete the entry (or convert it to `addressed:`).249 If ~30 days pass and nobody built coverage, that's "recommended but ignored" —250 convert it to a `noise:` skip note rather than re-emitting.251252### Close out253254**Summarize the run** — one paragraph: what you looked at, what you emitted, what you255remembered, what you ruled out and why. The harness writes that summary to the run row256as searchable prose; future runs read it via `signals-scout-runs-list`. Do **not** write257a separate "run metadata" scratchpad entry — the run summary already serves that role.258259## Disqualifiers (skip these)260261- **Builtin events without saved insights** — `$pageview`, `$autocapture`, `$identify`,262 `$set`, `$opt_in`, `$groupidentify`, `$feature_flag_called` are surfaced through263 PostHog's product views (Web Analytics, Feature Flags) without needing a custom264 insight. Don't recommend creating one.265- **Test events from internal users** — pin a `noise:observability_gaps:internal-distinct-ids`266 scratchpad entry for known internal distinct_ids and skip them in volume counts.267- **Events from disabled feature flags** — if the event only fires when a flag is268 disabled or only for a tiny rollout %, the volume is artificially low.269- **Events on ad-hoc one-off dashboards** — a private dashboard with one viewer doesn't270 count as "covered." Use the `popular_insights` viewer-count threshold.271- **Ambient app-shell telemetry** — an event whose distinct-user reach is roughly272 equal to `$pageview`'s fires for nearly every user as part of the app shell, not273 as a discrete feature metric. Zero saved insights on it is usually intentional;274 compare reach against `$pageview` before calling it a gap.275- **Deliberate engineering firehoses** — high-volume internal perf/telemetry events276 the team consumes via ad-hoc SQL or notebooks rather than saved insights. Before277 declaring zero coverage, check whether notebooks reference the event — covered by278 choice is not a gap.279- **Experiment-exposure events** — events that exist to drive an experiment's280 metrics are covered by the experiment itself. Don't recommend standalone insights281 for them while the experiment runs.282- **One-per-user lifecycle events** — onboarding, wizard, and setup events fire once283 per user; their volume is just signup flow-through and rarely deserves a284 standalone insight.285- **Time-boxed promotion / campaign events** — campaign-shaped events appear, spike,286 and end by design. Going quiet is not drift, and lacking coverage is not a gap287 unless the underlying surface (impressions + conversions) persists.288- **Incident-investigation scaffolding** — short-lived events created during an289 incident, often with incident-named insights attached. They stop firing when the290 incident closes; flagging the stoppage as drift is a false positive.291- **Legacy event-name variants** — insights that deliberately union an old and a new292 event name for historical continuity are well-maintained, not drifted. Read the293 insight's query JSON before declaring a dead event "still referenced."294295When in doubt, write a scratchpad entry instead of emitting. Recommendations have a296high panic radius for whoever owns the observability surface — false positives erode297trust fast.298299## MCP tools300301Direct calls (read-only):302303- `read-data-schema` — `kind=events` for volumes, `kind=event_properties` /304 `event_property_values` for cardinality and breakdowns.305- `query-trends` — confirm recent-window volume + reach numbers cited in evidence.306- `query-paths` — sequence detection for funnel candidates.307- `insights-list` — paginated insight catalog (use sparingly; SQL is faster).308- `dashboards-get-all` — active dashboards + tags.309- `event-definitions-list` — event-definition metadata: `verified` flag, `last_seen_at`,310 `created_at`, custom-vs-builtin marker.311- `alerts-list` — existing alert configurations and what events they target.312- `execute-sql` over `system.insights` / `system.dashboards` / `system.cohorts` —313 the fast path for "does an insight reference event X?" type queries.314315Harness-level:316317- `signals-scout-project-profile-get` — cold orientation snapshot. Has `top_events`,318 `popular_insights[13]`, `recent_dashboards`, `existing_inbox_reports` already.319- `signals-scout-scratchpad-search` / `signals-scout-scratchpad-remember` — durable steering.320- `signals-scout-runs-list` / `signals-scout-runs-retrieve` — what prior runs found.321- `signals-scout-emit-signal` — emit a recommendation finding.322323For deeper investigation playbooks, the sandbox image bakes upstream PostHog skills:324`posthog:querying-posthog-data` (HogQL syntax + system.\* search patterns) and325`posthog:exploring-autocapture-events` (custom-event vs autocapture distinctions, when326each lens applies).327328## When to stop329330- Scratchpad + recent runs + profile show every domain you've considered already has331 coverage or has been recommended → close out empty.332- A candidate matches a scratchpad entry with `addressed:` (recommendation actioned) or333 `noise:` (recommended but ignored) key prefix → skip with a one-line note.334- You've validated 1-2 high-confidence gaps and emitted them → close out, even if335 there's more you could look at. Quality over volume — recommendations are a budget,336 not a target.337338"Looked but found nothing meaningful" is a real outcome, not a failure. Every339recommendation that doesn't ship is one fewer false positive eroding the inbox.340
Full transparency — inspect the skill content before installing.