
Description
Audit a PostHog A/B experiment for a customer — verify config, exposure, attribution, and metrics. Trigger phrases include "audit [customer]'s experiment", "audit the [name] experiment", "check experiment setup for [customer]", "validate this A/B test", or any request to review whether an experiment is correctly wired up. Assumes you already have MCP access to the customer's project (typically via the impersonation flow set up by the `impersonate-audit` wrapper that ships with this plugin).
SKILL.md
Experiment Audit
Verify that a customer's experiment is actually collecting variant data, that downstream attribution survives the funnel, and that the metrics measure what the customer thinks they measure. Output is a Slack-ready writeup grouped by the four questions customers almost always ask.
Step 0 — confirm scope before running
The skill assumes the active PostHog MCP is scoped to the customer's project, not yours. Always start with:
"What project am I in? List the most recent 5 experiments."
If the project name looks like your own internal project (e.g. "PostHog App + Website", id 2), STOP — the impersonation isn't routing correctly. Re-run the wizard or check /mcp auth before continuing.
Step 1 — pull the experiment config
Use experiment-list with search to find the experiment by name. Then pull the full record. Capture:
- Status (running / draft / stopped / paused) and start date.
- Linked feature flag key and ID.
- Variants and traffic split. Variant names must match what the customer's code reads — bucketing bugs are usually case/typo mismatches.
- Holdout, bucketing key (
device_idvsuser_id), andensure_experience_continuity. - Exposure event — default
$feature_flag_calledor a custom event. - Primary and secondary metrics. Note action IDs, event names, breakdowns, conversion windows, attribution modes.
- Filter test accounts setting.
Step 2 — pull the feature flag config
For the linked flag:
- Release conditions — read each one carefully.
- Watch for two specific footguns:
- URL targeting via person property (
$current_url = ...) — uses the latest URL the person has been seen on, not the current page. Stale by definition. Always flag as a problem. - Exact-match on a path fragment —
$current_urlis captured as the full URL (https://host/path). Exact-matching/pathwill never hit.
- URL targeting via person property (
- Audience filters (desktop-only, geo, cohort). Verify they use person properties or group properties — not URL.
- Rollout percentage and any super-conditions.
ensure_experience_continuitysetting on the flag (this overrides the experiment-level setting).
Step 3 — verify exposure is happening
Pull $feature_flag_called events for the flag key since the experiment start date.
- Total count. If suspiciously low for the time elapsed, dig.
- Break down by
$feature_flag_response. Should split close to 50/50 between the variant names (e.g.control/test). Flag a sample ratio mismatch if imbalance exceeds ~5% with non-trivial volume. - If
$feature_flag_responsereturnsfalsefor most events, the user isn't being bucketed into the experiment at all — the release condition is rejecting them. This is the most common cause of "experiment shows 0 exposures." - Spot-check 5 raw event rows. Note the
$current_url,$device_type,$feature_flag,$feature_flag_response, anddistinct_id.
Step 4 — verify downstream attribution
For each metric event (CTA click, signup page visit, signup completion, conversion):
- Pull sample rows and confirm they carry the
$feature/<flag-key>property with a real variant value (controlortest), notfalseor missing. - Action-based metrics: check the action filters. If the variant renders different DOM IDs, the action must match all of them or one variant will artificially show 0 events. Action URL filters should match the production page, not a dev preview.
- Metric scoping: if the metric is too broad (e.g. "any
$pageviewcontaining/signup"), it will credit both variants for global traffic regardless of source. Suggest scoping by$feature/<flag-key>property or session entry pathname.
Step 5 — verify identity continuity
Cross-domain handoff (Webflow → app, marketing → product, etc.) is where attribution usually dies.
- Pick 5–10 users who reached the final funnel step (e.g. signup completion). Pull their event timeline.
- Confirm they have a prior
$feature_flag_calledevent with a real variant value. - Confirm
$identifyfires on the handoff. If users never have an$identifyevent, the anonymous device profile never stitches to the authenticated user — variant attribution is dead even with a perfectly fired flag. - Confirm bucketing key +
ensure_experience_continuitysettings together don't cause re-bucketing.device_idbucketing without continuity = same user on a new device looks fresh.
Step 6 — downstream conversion metric (trial activation / purchase / etc.)
Customers often have a primary conversion event that lives downstream (in their app or warehouse).
- Search the event schema for the expected event name. Try several variants (
plus_trial_activated,trial_started,subscription_created). - If not present, look for warehouse sources via
external-data-sources-list. Common pattern: Snowflake/Postgres table likeaccounts.trial_started_at. - Recommend the cleaner path: emit a server-side event from the app on activation. Easier than warehouse joins, faster signal, no schema fragility.
- Alternative: use the warehouse table as an experiment metric directly (supported for funnel + trend metrics).
Step 7 — common pitfalls to call out (regardless of what you found)
The customer's actual setup almost always has one of these:
- Person-property URL targeting (always wrong for this use case)
- Exact-match operators on full-URL person properties (never hit)
- Action metrics tied to dev URLs/selectors that won't fire on prod
- Global metrics ("any signup completion") that credit both variants equally
- Missing
$identifyon the marketing → product domain handoff device_idbucketing withoutensure_experience_continuity→ re-bucketing across sessions- Default 14-day conversion window too short for downstream conversion events
- Internal/test user filter not configured → QA traffic skews early days
- Sanity check exposure within 24h of launch — a 50/50 that shows <10 events in a week is a wiring bug, not a power problem
Output format
Group the report by the four standard customer questions. Lead with the worst finding:
:warning: [Experiment name] — audit findings
[One-paragraph TL;DR of the headline finding. Be direct.]
---
(a) Does the config look correct?
[Verdict + specific issues with evidence — event counts, sample values, etc.]
---
(b) How to verify attribution (once issues are fixed)
[Concrete steps the customer can run themselves.]
---
(c) What to change about attribution
[Numbered action list, priority order. Each item should be specific
enough that the customer's engineer can act on it directly.]
---
(d) Common pitfalls to watch for
[Subset of step 7's checklist relevant to this customer's setup.
Frame as general guidance, not as accusations.]
---
Bottom line: [one or two sentences. What's the single most important
fix that unblocks the experiment?]
Rules
- Read-only. Do not create insights, dashboards, actions, experiments, or modify any config. You are impersonating the customer's user — any writes land in their actual project.
- No fabrication. If you can't find the experiment or the data is empty, say so explicitly. Do not invent findings to fill the template.
- Cite real numbers. Every claim about exposure counts, sample ratios, or event volumes must come from a query you actually ran in this session.
- Surface ambiguity. If a setting could be intentional (e.g. low conversion window because conversion happens fast), note both interpretations and ask the customer to confirm.
- Match the customer's writing register. Customers using PostHog are usually technical — don't oversimplify. But avoid jargon shorthand they may not know yet.
When the audit is done
Remind the user to:
- Exit Claude Code
- Log out of Django Admin impersonation in their browser
- Optionally disable the posthog plugin:
claude plugin disable posthog
The impersonate-audit.sh wrapper handles step 3 prompts automatically on exit.
More skills from the skills repository
View all 110 skillsaccount-handover
draft sales account handover notes
Apr 16CommunicationsCRMPostHogSalesauditing-warehouse-data-health
audit PostHog data warehouse health
Jun 21AuditData EngineeringData QualityObservability +1copying-flags-across-projects
copy feature flags across PostHog projects
May 4DeploymentFeature FlagsPostHogdiagnosing-experiment-results
diagnose PostHog experiment results and anomalies
May 22A/B TestingAnalyticsData AnalysisDebugging +1diagnosing-missing-recordings
diagnose missing PostHog session recordings
Apr 22AnalyticsDebuggingFrontendObservability +1diagnosing-sdk-health
diagnose PostHog SDK health
Apr 27AnalyticsDebuggingObservabilityPostHog
More from PostHog
View publisheranalyzing-expensive-users
analyze expensive users in AI observability
posthog
Jul 9AnalyticsCost OptimizationObservabilityPostHogauditing-endpoints
audit PostHog project endpoints
posthog
Jun 8AnalyticsAuditPostHogauditing-warehouse-source-health
audit PostHog data warehouse source health
posthog
Jun 18AuditData WarehouseObservabilityPostHogauditing-warehouse-view-health
audit PostHog materialized view health
posthog
Jun 18AuditData WarehousePerformancePostHogauthoring-error-tracking-alerts
author PostHog error tracking alerts
posthog
Jun 18AlertingDebuggingObservabilityPostHogauthoring-log-alerts
author log alerts in PostHog
posthog
Jul 18AnalyticsMonitoringObservabilityOperations +1