Self-Serve Product Analytics with Claude Code, Step by Step
Date: July 6, 2026 • Author: Second Axis
You have a decision to make this week and a question standing between you and it: did the onboarding change move activation for SMB accounts? The data team's queue says three days. Your own SQL says two hours you don't have, and even if you find them, you're now the person who pulls numbers instead of the person who decides things. This is a routine complaint: one r/ProductManagement thread describes analytics-request turnaround blocking decisions outright, and another PM points out that knowing SQL doesn't free you, it just makes you the default data-pull person.
Can you build a self-serve analytics workflow with Claude Code? Yes. It can hold read-only connections to your warehouse and analytics tools, write and run SQL, follow committed metric definitions, run on a schedule, and keep history in git. What it will not do is make its own answers trustworthy. That part is engineering, and it is the center of this post, because a wrong number confidently delivered is worse than a slow one. The chat interface is the easy 10%. The verification architecture is the build.
What you're building
Two halves, one repo:
- A recurring metric pack. The same 15-20 product metrics (activation, retention by segment, feature adoption, funnel conversion) computed every Monday, with trend annotations, anomaly flags, and a diff against last week. Committed history, so in October you can ask "when did this start" and get a real answer.
- Ad hoc question answering. You ask "did the onboarding change move activation for SMB?" and Claude Code writes the query, runs it read-only against the warehouse, and returns the number with the SQL it ran attached. Every answer ships its receipt.
- A verifier agent that stands between any number and any decision: it re-derives the figure a second way when possible, cross-checks it against the metric pack baseline, and flags definition mismatches before you forward something wrong.
Why chat-with-data demos break in production
Every chat-with-data demo looks the same: type a question, get a chart, applause. Then it meets a real company and dies of three deficiencies.
No shared metric definitions. "Activation" at your company is a specific thing: a specific event, within a specific window, excluding internal accounts and that one migration cohort from March. A model guessing at it from table names will produce a plausible number that doesn't match the board deck, and now there are two activation numbers in circulation, which is worse than zero.
No receipts. When the tool returns "activation is 34%," what query ran? Which tables? Which filters? If nobody can inspect the SQL, nobody can catch the error, and the first publicly wrong number ends the experiment. Trust in an analytics system is not a vibe; it is the ability to audit.
No baseline memory. "Is 34% good?" depends on what it was last week and the twelve weeks before. A tool that computes each answer fresh has no idea whether a number is normal, drifting, or on fire.
Notice what's missing from that list: SQL generation. Models write competent SQL and have for a while. The bottleneck was never typing SQL. It was trust and consistency, and both are things you build as files in a repo, not features you get from a chat box.
Phase 0: Prerequisites
- Claude Code installed (
npm install -g @anthropic-ai/claude-code) and authenticated, on a Pro, Max, Team, or Enterprise plan if you want the scheduling and artifact publishing later. - A read-only path to your data. For product events, PostHog, Amplitude, and Mixpanel all ship official MCP servers (PostHog, Amplitude, Mixpanel). For the warehouse itself, Google's MCP Toolbox for Databases covers BigQuery and Postgres. Whatever the transport, the credential must be read-only; more below.
- A useful head start: Anthropic's knowledge-work-plugins include a Product Management plugin with a
/metrics-reviewcommand. Good scaffolding; it does not contain your semantic layer, your verifier, or your guardrails. Those are this playbook. - A dedicated git repo. The recurring design depends on committed history.
- A week of your calendar for the semantic layer. This is the real prerequisite, and the next phase explains why.
Phase 1: The repo and the semantic layer
product-analytics/
├── CLAUDE.md # rules for every session
├── .mcp.json # warehouse + analytics MCP connections
├── .claude/
│ ├── agents/
│ │ └── verifier.md # re-derives numbers before they ship
│ └── skills/
│ ├── metric-pack/SKILL.md # the weekly run
│ └── answer/SKILL.md # ad hoc questions with receipts
├── metrics/ # THE SEMANTIC LAYER (committed)
│ ├── activation.md # definition + canonical SQL
│ ├── weekly_active.md
│ ├── retention_28d.md
│ ├── ... # one file per metric, 15-20 total
│ └── quirks.md # known data landmines
├── packs/ # committed: one folder per weekly run
│ └── 2026-07-06/
│ ├── pack.md # the report
│ └── metrics.json # raw values, for next week's diff
└── answers/ # committed: ad hoc Q&A with SQL attached
The metrics/ directory is the whole ballgame. Each file is a metric definition a human signed off on: prose description, the canonical SQL, which tables are sources of truth, and known caveats. For example:
# activation.md
Definition: account completes 3+ core actions within 14 days of signup.
Core actions: project_created, invite_sent, integration_connected.
Source of truth: warehouse.events (NOT the analytics tool's copy,
which drops events during SDK outages).
Exclusions: internal domains, accounts migrated 2026-03 (backfilled
signup dates are wrong).
Canonical SQL: [the query, in full]
And quirks.md holds the tribal knowledge that otherwise lives in one analyst's head: the timezone the events table actually uses, the double-counting bug before the April fix, the test accounts that never got flagged. Every wrong number you catch later becomes a line in this file.
This is the artifact that chat-with-data tools structurally lack. When definitions are committed files, every answer, whether from the weekly pack or a 2pm ad hoc question, computes "activation" the same way, and a definition change is a reviewed PR instead of a silent drift. CLAUDE.md enforces it:
## Rules
- Metrics are computed ONLY from definitions in metrics/. If a
question names a metric with no file, say so and propose a
definition; never improvise one.
- Every answer includes the exact SQL that produced it.
- Read quirks.md before writing any query.
- All warehouse access is SELECT only.
Budget real time here. Writing 15-20 definitions means chasing down what "activation" actually meant in three different decks. That work was always owed; this system just refuses to run without it.
Phase 2: Warehouse wiring and guardrails
Wire the connections in .mcp.json (project-scoped, committed, so every teammate and every scheduled run gets the same setup): your analytics tool's MCP server for event drill-downs, the warehouse via MCP Toolbox or a thin query script. Then put the guardrails in before the first question, not after the first incident:
- Read-only credentials, enforced at the database. Create a warehouse role with SELECT on allowlisted schemas only. Not a prompt instruction; a grant. Prompts are advice, permissions are physics.
- Tool allowlisting. Interactive sessions inherit project permission settings; headless runs get
--allowedToolsscoped to the query tools and file operations. Nothing in this workflow needs write access to anything but the repo. - Row limits and cost caps. A generated query with a missing join condition can scan your whole events table. Set per-query byte/cost limits at the warehouse level where supported, and make CLAUDE.md require a
LIMITand a date-range predicate on every exploratory query. - No PII in outputs. Answers and packs get committed and shared. Blocklist PII columns (emails, names, IPs) in the metric definitions and have the verifier check outputs for them.
Phase 3: The metric pack skill
.claude/skills/metric-pack/SKILL.md
---
name: metric-pack
description: Compute the weekly product metric pack with diffs
---
Today's pack dir: packs/$(date +%F)/
1. PRIOR STATE: read the latest packs/*/metrics.json. These are
this week's baselines.
2. COMPUTE: for every file in metrics/, run its canonical SQL.
Do not modify queries; if one errors, record the failure and
continue. Write raw values to metrics.json.
3. ANNOTATE: for each metric, compare to baseline and the trailing
8 weeks. Flag anomalies (>2 standard deviations from trend, or
any metric whose query failed).
4. DRILL-DOWN STARTERS: for each flagged anomaly, write one
segment-level breakdown query and its result, so a human starts
from evidence, not from scratch.
5. VERIFY: launch the verifier agent on the pack. Fix or annotate
anything it flags. Max 2 loops.
6. PERSIST: write pack.md and metrics.json. Commit.
Step 2's "do not modify queries" rule matters. A scheduled run that quietly rewrites a failing metric query to make it pass is a system that lies on Mondays. Failures get reported, and a human fixes the definition file.
Run it interactively a few times before scheduling. The first run will surface a wrong definition or a quirk you forgot; that is the system working.
Phase 4: Ad hoc answers and the verifier
The /answer skill handles the 2pm questions. Its contract: parse the question against metrics/ definitions, write the query, run it read-only, and return the number with the SQL attached, always. It also carries one non-negotiable line: any number that feeds a decision doc gets re-computed through the verifier first. Quick Slack curiosity can go out with just its receipt; anything that changes a roadmap gets the second pass.
.claude/agents/verifier.md
---
name: verifier
description: Re-derives and cross-checks numbers before they ship
tools: Read, Bash
---
You receive an answer: a question, a number, and the SQL that
produced it. Checklist:
1. DEFINITION MATCH: does the SQL implement the metric as defined
in metrics/? If the question said "weekly active" but the query
computes activation (or vice versa), FAIL with the mismatch
quoted from the definition file.
2. SECOND DERIVATION: recompute the number a different way when
possible (different source table, different aggregation path,
or the analytics tool's MCP as a cross-check). Within tolerance:
note both figures. Outside: FAIL.
3. BASELINE SANITY: compare against the latest packs/*/metrics.json.
A number 3x its baseline is either a finding or a bug; say which
and show why.
4. QUIRKS: confirm the exclusions in quirks.md were applied.
5. NO PII columns in the output.
Return PASS with the cross-check evidence, or FAIL with the
specific fix. Never soften a FAIL into a caveat.
Check 1 is the one that earns its keep. The most dangerous wrong answers aren't SQL bugs; they're correct queries against the wrong definition. "You asked for weekly active but the metric file defines activation differently, and here is the difference" is exactly the sentence a good analyst would say and a chat interface never does.
Phase 5: Scheduling and state
Every scheduled run is an individual session; continuity is a mechanism you build, and here it is the repo itself. packs/ is the baseline memory, metrics/ is the definitional memory, answers/ is the audit trail. The skill reads prior state as step 1, so each Monday's run inherits everything by cloning the repo.
The managed path: run /schedule and create a weekly routine, "Mondays 6am: run /metric-pack." It executes in a cloud sandbox that clones the default branch, loads .claude/ and .mcp.json, and pushes commits on a claude/ branch. Put the warehouse credential in the routine's environment variables, and keep it the same read-only role, not a convenient admin key. Self-hosted alternatives are cron plus headless mode (claude -p "/metric-pack" --allowedTools ...) or a GitHub Actions cron with claude-code-action. Either way, add a Stop hook that posts run status to Slack, because a metric pack that silently stopped is a team confidently reading stale baselines.
Phase 6: Sharing with the team
- Artifact publish for the weekly pack. Claude Code renders
pack.mdas a hosted page on claude.ai with a stable URL; org-scoped visibility needs a Team or Enterprise plan, and publishing works from the CLI and desktop app rather than every automated surface, so in a fully scheduled setup this step may keep a human in the loop. - Slack for anomaly alerts. The pack is a page; anomalies are interrupts. Have the skill post only the flagged metrics with their drill-down starters, linking to the full pack.
- Answers travel with their receipts. When someone pastes a number into a doc or a thread, the SQL goes with it. This is culture as much as tooling: the receipt is what lets a skeptical engineer check the work instead of relitigating the number in a meeting.
Phase 7: Maintenance (what you own now)
- Schema drift. An events-table migration breaks metric SQL silently. The pack skill's "record failures, don't rewrite" rule surfaces it; a human updates the definition file the same week.
- Definition governance. Sales and product will eventually disagree about what counts as "active." The metrics file forces the argument into one reviewable place, but a person still has to own the ruling and the changelog.
- Quirk documentation. Every wrong number traced to a data landmine becomes a
quirks.mdentry, or the system relearns it the expensive way. - Credential rotation. The read-only role's key lives in the routine's environment; rotations now include it.
- Warehouse cost watch. Generated queries are your compute bill. Review the query log monthly; the cost caps from Phase 2 bound the blast radius, not the waste.
What this costs
The token math is the boring part. A weekly pack run is 15-20 canned queries plus annotation and a verifier pass; ad hoc answers are a query, a receipt, and sometimes a second derivation. All of it sits comfortably inside a subscription's included usage, and it is trivial next to the warehouse compute the queries themselves consume, which is where the metered money actually goes and why the cost caps exist.
The real costs are two, and neither shows up in /usage. First, the read-only infrastructure: a scoped warehouse role, allowlisted schemas, credential plumbing into the routine. That is a data engineer's afternoon plus ongoing rotation duty. Second, and much larger, the semantic layer: someone has to write down what 15-20 metrics actually mean, resolve the contradictions between decks, and keep the files current as the product changes. That is days of focused work up front and a standing governance duty after. Say it plainly: you are not buying an analytics tool, you are authoring your company's metric definitions and getting a tireless analyst who follows them.
The four practical challenges
- Setup at scale: not data volume this time, but definitional scale. The semantic layer (Phase 1) and the guardrails (Phase 2) are the setup, and the system is only as good as those files.
- Sharing with the team: artifact publishing for the pack, Slack for anomalies, and receipts attached to every number that travels (Phase 6), with the plan-tier and automation caveats that come with publishing.
- Maintenance: schema drift, definition disputes, quirk capture, credential rotation, warehouse spend (Phase 7). Definition governance is the one that never ends.
- Cost: trivial in tokens, real in warehouse compute, and largest in the human work of authoring and governing the metric definitions.
The managed alternative
Ask-your-data features are shipping inside the analytics tools themselves: PostHog, Amplitude, and Mixpanel are building natural-language querying into their products, and BI platforms like ThoughtSpot sell it as the product. The trade is fair and worth stating. They are fast for questions their data model covers, with zero setup and a supported UI. The thin parts are exactly the parts this playbook centers: questions that span sources (product events joined to billing joined to CRM), your metric definitions rather than their defaults, and receipts-attached answers you can audit and re-derive. If your questions live inside one tool's model, use the tool. The build above exists for the questions that don't.
Doing this with Second Axis
Everything above is the do-it-yourself path. On Second Axis, self-serve product analytics is a workflow in the marketplace: connect your analytics and warehouse, choose the workflow, and set the goal ("weekly metric pack Mondays plus on-demand answers with the query attached"). It runs on schedule from there, keeps its baselines, and monitors itself: broken schemas, failed runs, and numbers that fail verification get found and alerted instead of quietly shipped.