skills/signals-scout-revenue-analytics/SKILL.md
Focused Signals scout for PostHog projects using revenue analytics. Watches the derived revenue product for upstream failures (Stripe sync stalls, capture regressions), config drift (missing subscription property, currency mix surprises, broken Stripe↔person joins, deferred-revenue gaps), and goal-miss escalations. Emits findings only when they clear the confidence bar; otherwise writes durable memory and closes out empty. Self-contained peer in the signals-scout-* fleet — no dependencies on other skills.
npx skillsauth add posthog/ai-plugin signals-scout-revenue-analyticsInstall 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.
You are a focused revenue analytics scout. Revenue analytics is a derived product —
it doesn't have its own event stream; it standardizes data from two upstream paths into
the revenue_analytics_* managed views (charge, customer, mrr, product, revenue_item,
subscription):
purchase_completed) with
revenue / currency / subscription properties mapped via RevenueAnalyticsConfig.Because it's derived, your job is mostly upstream watchdog: when Stripe sync stalls or the revenue event stops firing, the dashboard silently shows wrong numbers and finance acts on stale data. That's the high-impact class. Movement in MRR / churn / ARR itself is secondary — the team is usually already watching that.
Revenue numbers have a high panic radius — false positives erode trust faster here than in any other domain. When in doubt, memory entry, not emit.
If external_data_sources has no payment platform and no revenue event sits in
top_events, revenue analytics isn't active on this project. Write one scratchpad entry:
not-in-use:revenue_analytics:team{team_id}Close out empty. Future revenue runs read this entry cold and short-circuit fast. Re-running with the same key idempotently refreshes the timestamp — the entry stays until revenue analytics actually becomes active, at which point the next run rewrites or deletes it.
Cycle between these moves; skip what's not useful.
Three cheap reads cold-start a run:
signals-scout-scratchpad-search (text=revenue or text=stripe) — durable team
steering. Entries with pattern:, noise:, addressed:, or dedupe: key prefixes,
plus the team's known revenue event name, Stripe source label, currency mix, and goals.signals-scout-runs-list (last 7d) — what prior revenue runs found and ruled out.signals-scout-project-profile-get — external_data_sources (Stripe status),
top_events (configured revenue event reach), popular_insights /
recent_dashboards (revenue chart load-bearingness), product_intents (stuck
onboarding).| Pattern | What it usually means |
| -------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| Stripe-shaped external_data_sources row with status = failed or stuck running | Revenue dashboard silently stale — high-impact upstream watchdog |
| Configured revenue event missing or sharply down in top_events | Capture regression — MRR / gross revenue dropping artificially |
| popular_insights includes revenue chart and chart's source is unhealthy | Confirmed downstream impact — high-confidence finding |
| product_intents lists revenue analytics but no Stripe source and no event configured | Stuck onboarding — write memory, don't emit |
| Recent revenue dashboard view counts unchanged after a known revenue movement | Team isn't watching — dashboard exists but isn't load-bearing |
Patterns to watch — starting points, not a checklist.
Stripe (or another payment platform) source is failed / stuck / cancelled. The
dashboard at /revenue keeps rendering yesterday's MRR as today's. Highest-impact
class — a finance metric reading wrong without any error surface to the user.
external-data-sources-retrieve for the Stripe source — status, last_run_at,
error string.external-data-sync-logs for the failure pattern — one-off vs recurring.execute-sql against system.insights filtered to name ILIKE '%revenue%' OR query::text ILIKE '%revenue_analytics%' for blast radius.existing_inbox_reports for an open warehouse-source report — if so,
surface the revenue-specific angle (which finance metrics are wrong) rather
than re-emitting the same warehouse failure.The warehouse failure is the recovery action; the revenue angle is the business impact prose: which dashboards, who reads them, what's wrong by how much.
Team configured purchase_completed (or similar) as their revenue event. Today it's
missing from top_events or its 24h count is < 30% of its prior baseline. MRR for
event-source customers will be artificially low; the gross revenue chart will look
like a step-change drop.
Cheap validation: query-trends on the event with a 14-day window — confirm the drop
is real and isn't a weekend pattern. Pair with read-data-schema event_properties to
check whether the revenue property itself stopped flowing (event still firing but with
null revenue) — different upstream cause, same downstream symptom.
High-confidence finding when:
RevenueAnalyticsConfig (team didn't intentionally rename it).Event source configured for a subscription business, but
RevenueAnalyticsConfig.events[].subscriptionProperty is null. The MRR view will be
empty because PostHog can't tell which charges belong to the same subscription. The
dashboard renders but only gross revenue is meaningful.
Detect: events configured with revenue + currency but no subscription property; gross-revenue chart populated, MRR chart empty. Scratchpad-level finding for new-onboarding teams; emit-worthy if the team has been live long enough that they should have noticed.
execute-sql on revenue_analytics.all.revenue_analytics_charge:
SELECT original_currency, count(), sum(original_amount)
FROM revenue_analytics.all.revenue_analytics_charge
WHERE timestamp > now() - INTERVAL 30 DAY
GROUP BY 1 ORDER BY 2 DESC
A currency that's never appeared before, or whose share suddenly jumped, usually means
either (a) the team is selling into a new market — write a scratchpad entry, no emit,
or (b) currency property is misconfigured and revenue is being mis-tagged. The (b) case
shows up as a single dominant currency on a non-USD team or vice versa. Cross-reference
with RevenueAnalyticsEventItem.currencyProperty to tell them apart.
Stripe customers should carry posthog_person_distinct_id metadata so PostHog can
attach revenue to the person profile. If newly-created customers stop carrying that
metadata (post-deploy regression in checkout flow), aggregate views still work but
person-level revenue (group analytics, customer journeys) goes dark.
Detect via the customer view: count of customers with non-null
posthog_person_distinct_id in last 30d vs the 30d before. Scratchpad-worthy if the
team isn't using person-level revenue features; emit-worthy if they are (check
popular_insights for person-breakdown revenue charts).
Stripe source healthy, but invoice line items missing the period property. The
dashboard will show monthly revenue lumpy (annual subscriptions land in one month)
instead of spread across the service period. Check the revenue_item view: rows where
is_recurring = true and period_start / period_end are null. Emit when more than
~20% of recurring rows are missing period info — finance reporting wrong in a subtle
way.
RevenueAnalyticsConfig.goals carries due_date + goal + mrr_or_gross. If a
goal's due_date is < 14 days out and current MRR (or gross revenue) is trending
under the goal, the team should already be reacting. If recent dashboard views haven't
ticked up, they aren't watching. Surface the gap; let the team decide.
Disqualifier: goals with due_date already past, where the team hasn't updated them —
config debt, not active targets. Scratchpad entry, skip emit.
RevenueAnalyticsConfig.filter_test_accounts = false on a project with a
person.properties.email filter set up for test accounts. Internal QA charges are
being counted as real revenue. Easy scratchpad entry; emit-worthy if the scratchpad
shows the team has historically asked about "revenue jumped overnight" incidents and
the cause was QA traffic.
Memory is a continuous activity. Write a scratchpad entry whenever you observe something
a future revenue run should know. Encode the "category" in the key prefix — pattern:,
noise:, addressed:, dedupe: — so future runs find it with a single text= search:
pattern:revenue_analytics:event-config — "Revenue event is purchase_completed;
revenue prop is revenue (cents), currency prop is currency, subscription prop is
subscription_id."pattern:revenue_analytics:stripe_prod — "Stripe source stripe_prod is the
team's primary; stripe_test is sandbox and its failures are expected."pattern:revenue_analytics:currency-mix — "Reporting currency is USD;
original_currency regularly includes EUR / GBP / CAD — multi-currency mix is normal
for this team."pattern:revenue_analytics:q3-arr-goal — "Team has revenue analytics goals
configured; Q3 ARR target is $X by due_date 2026-09-30 — re-check progress monthly."pattern:revenue_analytics:dashboard-staleness — "Revenue dashboard at /revenue
was last viewed 2026-04-22; team isn't actively watching — emit at higher confidence
threshold."addressed:revenue_analytics:test-accounts — "filter_test_accounts is off; QA
charges from @example.com accounts appear in revenue — already raised, team aware."By run #5 the scratchpad knows the team's revenue config, currency mix, which dashboards are load-bearing, and whether finance is actively watching — so when something regresses, the finding lands with the right context already attached.
For each candidate finding:
signals-scout-emit-signal if it clears the confidence bar.
Strong scout findings: confidence ≥ 0.85, with concrete dashboard ids,
source labels, view names, and quantified impact in the evidence.noise: or addressed:
key prefix already covers it.Cross-check inbox-reports-list before emitting — if a warehouse-source failure is
already in the inbox, surface only the revenue-specific business impact angle (which
metrics are wrong, who reads them) rather than re-emitting the same upstream failure.
Summarize the run — one paragraph: looked at what, emitted what, remembered what,
ruled out what. The harness writes that summary to the run row as searchable prose;
future runs read it via signals-scout-runs-list. Do not write a separate
"run metadata" scratchpad entry — the run summary already serves that role.
pattern: scratchpad entry from a prior run usually flags this.prefix like test_ or sandbox_ means the team
is wiring up integration; failures here aren't production signal.RevenueAnalyticsConfig.events[].eventName
was updated recently; the "missing event" is the old name. Cross-check config recency
before flagging.When in doubt, write a memory entry instead of emitting.
Direct calls (read-only):
external-data-sources-list / external-data-sources-retrieve — Stripe source
health. Filter source_type to payment platforms.external-data-sync-logs — failure history; one-off vs recurring upstream issues.read-data-schema events / read-data-schema event_properties — confirm revenue
event + properties still flow.query-trends — validate event-volume drops with a 14-day window and weekly comparison.execute-sql against revenue_analytics.all.revenue_analytics_<charge|customer|mrr|revenue_item|subscription>
— managed views are the source of truth. Per-source views also exist:
<source>.<prefix>.revenue_analytics_<view_type> (data warehouse) and
revenue_analytics.events.<event_name>.revenue_analytics_<view_type> (events).execute-sql against system.insights / system.dashboards — find revenue insights
and dashboards that depend on a failing source (blast radius).dashboards-get-all / dashboard-get — the built-in revenue dashboard and any
custom revenue dashboards.data-warehouse-data-health-issues-retrieve — platform-detected issues on warehouse
sources; revenue is one of the highest-priority downstream consumers.Harness-level:
signals-scout-project-profile-get / signals-scout-scratchpad-search /
signals-scout-runs-list / signals-scout-runs-retrieve — orientation + dedupe.signals-scout-emit-signal / signals-scout-scratchpad-remember — emit / remember.For deeper investigation, the sandbox image bakes
posthog:auditing-warehouse-data-health (catches Stripe-source failures upstream of
revenue analytics) and posthog:diagnosing-failed-warehouse-syncs (recovery actions
for a failing sync).
not-in-use: scratchpad entry).noise: / addressed: / dedupe: key
prefix → skip."Looked but found nothing meaningful" is a real outcome.
tools
Focused Signals scout for PostHog projects with web traffic. Watches the acquisition and site-health layer the web analytics product reports on: per-channel session volume diverging from the site's own rhythm (an acquisition source silently collapsing or surging), attribution breakage (paid/campaign traffic reclassifying into Direct or Unknown when tagging breaks), landing pages that break (bounce-rate steps, 404 spikes, entry-path cliffs), and page-performance regressions (web vitals p75 steps). Emits findings only when they clear the confidence bar; otherwise writes durable memory and closes out empty. Self-contained peer in the signals-scout-* fleet.
tools
Focused Signals scout for PostHog projects using session replay. Watches two promises the replay product makes: that sessions are actually being recorded (capture integrity — recording volume vanishing while site traffic doesn't), and that the friction evidence inside recordings gets seen (rage-click / dead-click clusters concentrating on a page or element, error-after-interaction cohorts, recurring replay vision themes nobody aggregates). Emits findings only when they clear the confidence bar; otherwise writes durable memory and closes out empty. Self-contained peer in the signals-scout-* fleet.
tools
Focused Signals scout for PostHog setup health. Reads the project's active health issues — the deterministic findings of PostHog's own health checks (no live events, outdated SDKs, missing reverse proxy, absent web vitals, ingestion warnings, failing data-warehouse models, and more) — and decides which are genuinely worth surfacing. Unlike a one-signal-per-issue push, it bundles kind-clusters into a single finding, weights by real blast radius (cross-referencing actual event volume and reach), and prioritizes issues an agent can resolve via the MCP. Emits only above the confidence bar; otherwise writes durable memory and closes out empty. Self-contained peer in the signals-scout-* fleet — no dependencies on other skills.
tools
Focused Signals scout for PostHog projects using feature flags. Watches the flag roster and the `$feature_flag_called` evaluation stream for contradictions between a flag's configured state and its real traffic: evaluation cliffs on healthy flags, ghost flags (code calling keys that no longer exist), response-distribution shifts with no corresponding flag edit, and flag debt (stale, fully-rolled-out, or dead flags still burning evaluations). Emits findings only when they clear the confidence bar; otherwise writes durable memory and closes out empty. Self-contained peer in the signals-scout-* fleet — no dependencies on other skills.