← All workflow playbooks

Churn Analysis with Claude Code, Step by Step

Date: July 6, 2026 • Author: Second Axis

Your product has 10,000 users, churn ticked up last quarter, and the answer is sitting somewhere in your event data: which cohort is bleeding, what those users stopped doing before they left, what changed after the last release. You want that diagnosis on your desk every Monday morning, not a one-off analysis that's stale by the next sprint.

Can you build that with Claude Code? Yes. It has every primitive you need: persistent memory, subagents, skills, scheduled routines, MCP connections to your analytics stack, and publish-to-web artifacts. What it doesn't do is assemble them into a churn pipeline for you. That part is engineering, and this post is the complete playbook for it: the data pipeline, the clustering, the analyst agents, the scheduled runs that remember last week's findings, and the report your team actually reads. Every step, no hand-waving.

Follow it to the end and you'll have something genuinely good. Writing every step down is also the point: by the end you'll know exactly what "it" takes.


What you're building

The target output, produced every Monday morning without you touching anything:

  • Users grouped into 5-8 behavioral cohorts (not the demographic segments you guessed at, but clusters that emerge from actual usage).
  • Per cohort: churn rate, trend vs. last week, and the behavioral signature of users who churned vs. retained.
  • A ranked list: which cohorts contribute most to churn, with evidence.
  • A diff against last week's run, with new risks flagged and resolved risks closed out, so the report never repeats itself.
  • A shareable web page your team can open without installing anything.

Now let's build it.


Why this can't be one prompt

A 10,000-user product generates something like 200-1,000 events per user per month. Call it 5 million events. Serialized as JSON, an event is roughly 25-40 tokens. That's 125-200 million tokens of raw data.

Claude's context window is 200K tokens (1M in extended mode). So the raw data is about 150-1,000× larger than the context window. No prompt, no model upgrade, no clever compression changes this. And even if you could stream it all through, paying per-token to have a language model read raw event logs is the most expensive possible way to compute an aggregate.

The correct architecture, the one every practitioner lands on, is:

  1. Code touches the raw data. Claude Code writes and runs Python/SQL that crunches 5M events into small aggregate tables.
  2. The model touches the aggregates. Cohort summaries, feature matrices, and diffs are a few thousand tokens each; those go to Claude for interpretation.
  3. Agents parallelize the interpretation. One analyst subagent per cohort, then a synthesizer, then a critic.

Claude Code is great at step 1; writing the crunching code is exactly what it's built for. Steps 2 and 3 are where you do real setup work. Here's all of it.


Phase 0: Prerequisites

Before anything else:

  • Claude Code installed (npm install -g @anthropic-ai/claude-code) and authenticated. For the scheduled/publishing parts of this playbook you'll want a Pro, Max, Team, or Enterprise plan, since cloud routines and artifact publishing are plan features.
  • Access to your event data. Two viable paths:
    • Analytics API export: PostHog, Amplitude, and Mixpanel all have export APIs, and all three now ship official MCP servers. We'll wire PostHog's up below.
    • Warehouse access: if events land in Snowflake/BigQuery/Postgres, a read-only service account is the cleaner path at this scale (Google's MCP Toolbox for Databases covers BigQuery and Postgres).
  • A useful head start: Anthropic's open-source knowledge-work-plugins include a Product Management plugin (/metrics-review, /synthesize-research, etc.) and a Data plugin. Install them; they're good scaffolding. Be clear about what they don't contain: the clustering pipeline, the cohort agents, the run-to-run state, and the scheduling below. That part is this playbook.
  • Python 3.11+ with duckdb, pandas, scikit-learn available (Claude Code will write the scripts; these need to be installable).
  • A git repo dedicated to this analysis. Not optional: the recurring-run design later depends on committed history.

Phase 1: The analysis repo

Create a repo with this structure. This is the skeleton everything else hangs on:

churn-analysis/
├── CLAUDE.md                  # project brief for every session
├── .mcp.json                  # analytics MCP connections (team-shared)
├── .claude/
│   ├── agents/
│   │   ├── cohort-analyst.md  # per-cohort analysis subagent
│   │   ├── synthesizer.md     # merges cohort reports
│   │   └── critic.md          # grades the draft before publishing
│   └── skills/
│       └── churn-analysis/
│           └── SKILL.md       # the orchestration recipe
├── scripts/
│   ├── export_events.py       # pulls events → data/events.parquet
│   ├── build_features.py      # per-user behavioral feature matrix
│   └── cluster_users.py       # k-means/HDBSCAN → cohort assignments
├── data/                      # gitignored: raw & intermediate data
├── runs/                      # committed: one folder per weekly run
│   └── 2026-07-06/
│       ├── cohort_summaries/  # small aggregate JSON per cohort
│       ├── report.md          # the published report
│       └── state.json         # open risks, cohort definitions, baselines
└── .gitignore                 # data/, *.parquet, .env

The split between data/ (gitignored, heavy) and runs/ (committed, light) is the load-bearing decision. Raw events never enter git or the model's context. Run outputs, a few hundred KB of markdown and JSON, are committed, because committed history is how next week's run knows what this week's run found. Hold that thought for Phase 6.

CLAUDE.md

This file is read at the start of every session, including scheduled ones. Keep it under ~200 lines; it's instructions, not documentation:

# Churn Analysis Pipeline

## What this repo does
Weekly behavioral churn analysis for [PRODUCT]. Raw events are
processed by scripts in scripts/. NEVER read raw event files
into context. Work only from aggregates in runs/<date>/.

## Definitions (do not improvise these)
- Churned: no session in trailing 28 days, previously active ≥2 weeks
- Active: ≥3 sessions/week
- Cohorts: behavioral clusters from scripts/cluster_users.py,
  NOT pricing tiers or signup dates

## Data flow
1. scripts/export_events.py  → data/events.parquet
2. scripts/build_features.py → data/user_features.parquet
3. scripts/cluster_users.py  → data/cohort_assignments.parquet
                             + runs/<date>/cohort_summaries/*.json

## Rules
- Every claim in a report must cite a number from a cohort summary
- Compare against the previous run in runs/ before writing anything
- Never modify scripts/ during a scheduled run; flag issues instead

.mcp.json

Project-scoped MCP config is committed, so every teammate (and every cloud session) gets the same connections. PostHog example:

{
  "mcpServers": {
    "posthog": {
      "type": "http",
      "url": "https://mcp.posthog.com/mcp"
    }
  }
}

One note on MCP at this scale: MCP tools are how Claude runs targeted queries ("weekly active users for cohort 3"), not how you move 5M events. Bulk export goes through scripts/export_events.py hitting the batch export API with a service key in .env. Use MCP for interactive drill-downs, the script for the pipeline.


Phase 2: The data pipeline

Open Claude Code in the repo and have it write the three scripts. You still have to specify them precisely. This is a session of real back-and-forth: reviewing code, running it, fixing schema mismatches.

export_events.py pulls the trailing 90 days of events into data/events.parquet. Requirements to give Claude: paginated export (the API will not hand you 5M events in one response), incremental mode (only fetch days not already local), and a --full flag for rebuilds.

build_features.py collapses events into one row per user. This is the step that makes clustering meaningful, so the feature list is a product decision, not a coding detail. A solid starting set:

sessions_per_week, days_since_last_session, tenure_days,
core_feature_uses_7d / _28d, distinct_features_used,
support_tickets_28d, error_events_28d,
invite_sent_count, integration_connected (bool),
weekly_trend_slope (sessions, last 8 weeks)

DuckDB does this over 5M parquet rows in seconds on a laptop. This is the "scale beyond context" problem solved with 40 lines of SQL; the model never sees a raw event. This pattern isn't ours, either. It's how practitioners handle big data with Claude Code generally: Yale economist Paul Goldsmith-Pinkham documents processing 70GB of mortgage data this way, DuckDB plus parquet, with schema metadata stored in the database so Claude queries structure instead of scanning rows.

cluster_users.py standardizes features, runs k-means for k=4..10, picks k by silhouette score (expect 5-8 for a typical B2B SaaS), writes per-user assignments plus, critically, a small JSON summary per cohort: size, churn rate, feature medians, top-decile behaviors, 15 example user IDs. Those summaries are each 1-3K tokens. They are what the agents read.

Run the pipeline end to end once, manually, and sanity-check the clusters (if one cohort is 92% of users, your features aren't discriminating; iterate). Do not proceed to agents until the numbers look right. Agents interpreting bad clusters produce confident nonsense.


Phase 3: The subagents

Subagents live in .claude/agents/, one markdown file each, and this is where analysis quality is actually engineered. Three roles:

.claude/agents/cohort-analyst.md

---
name: cohort-analyst
description: Analyzes one behavioral cohort's churn in depth
tools: Read, Bash, Grep
model: inherit
---
You analyze exactly ONE cohort, specified in your task prompt.

Work from runs/<date>/cohort_summaries/<cohort>.json plus targeted
DuckDB queries against data/*.parquet via Bash (SELECT only).

Produce:
1. Churn rate and 4-week trend
2. Behavioral signature: churned vs. retained users in this cohort,
   which features' usage diverges first, how many days before churn
3. One primary hypothesis with supporting numbers
4. What you could NOT verify with available data

Rules: no claims without a number. If sample size < 30, say so
instead of trending. Return findings as structured markdown.

.claude/agents/synthesizer.md reads all cohort reports, ranks cohorts by absolute churn contribution (cohort size × churn rate, not just rate; a 40% churn rate in a 50-user cohort matters less than 8% in a 4,000-user cohort), merges duplicate hypotheses, and drafts the report.

.claude/agents/critic.md is the judge loop. Its system prompt is a checklist:

---
name: critic
description: Grades the churn report draft before it ships
tools: Read, Bash
---
Grade the draft report against:
1. Does every claim cite a number that exists in cohort_summaries/?
   Spot-check 5 claims by re-running the query.
2. Does it use data from the last 14 days, or is it stale?
3. Does it diff against the previous run in runs/, or repeat it?
4. Is any "insight" just the cohort definition restated?
Return PASS, or FAIL with the specific fixes required.

That last critic check matters more than it looks: clustering-based analysis loves to report "the low-engagement cohort has high churn" as a finding. It's a tautology. The critic is how you keep tautologies out of the report your CEO reads.


Phase 4: The orchestration skill

A skill is a reusable recipe Claude Code follows when you (or a schedule) invoke /churn-analysis. It's the difference between "I prompt it carefully from memory every Monday" and "the process is versioned in git."

.claude/skills/churn-analysis/SKILL.md

---
name: churn-analysis
description: Run the full weekly churn analysis pipeline
---
Run the weekly churn analysis. Today's run dir: runs/$(date +%F)/

1. PRIOR STATE: read the most recent runs/*/state.json and
   report.md. Note open risks and last week's cohort baselines.
2. PIPELINE: run export_events.py (incremental), build_features.py,
   cluster_users.py. If cohort count or sizes shifted >20% vs.
   state.json, flag "cohort drift" prominently in the report.
3. FAN OUT: launch one cohort-analyst subagent per cohort,
   in parallel. Pass each its summary JSON path and the matching
   cohort section from LAST week's report.
4. SYNTHESIZE: launch synthesizer with all analyst outputs +
   last week's report. Draft must include a "Changed since last
   week" section.
5. CRITIQUE: launch critic on the draft. If FAIL, return the
   fixes to synthesizer. Max 2 revision loops, then ship with
   critic notes appended.
6. PERSIST: write report.md and updated state.json (open risks,
   baselines, cohort definitions) into the run dir. Commit.
7. PUBLISH: render the report as an artifact page and publish.

Note steps 3-5 encode the multi-agent pattern explicitly. Claude Code runs subagents in parallel when asked, and each analyst gets its own context window, so eight cohorts don't compete for the same 200K tokens. That is the whole reason this scales.


Phase 5: First real run

Invoke /churn-analysis interactively and stay in the loop. Expect to iterate on:

  • Analyst quality. If a cohort report is thin, the fix is usually in the summary JSON (add the missing aggregate) or the agent prompt (demand the missing comparison), not in re-prompting harder.
  • Critic strictness. Too lenient = tautologies ship. Too strict = infinite revision loops. Tune the checklist.
  • Cost. Run /usage after the session. The pipeline scripts cost nothing (they're just Python); the token spend is the fan-out. Eight analysts reading 2K-token summaries is cheap; eight analysts told to "explore the data freely" is not. Keep agents on rails.

Only schedule it once an interactive run produces a report you'd actually forward.


Phase 6: Making it recurring (the part everyone underestimates)

Claude Code has scheduling. This is where you wire it, and where the state-passing design from Phase 1 pays off.

The core problem: every scheduled run is an individual run. A fresh session does not remember what last Monday's session concluded. If you naively schedule "analyze churn," you get the same analysis every week, open risks silently vanish, and nobody notices cohort drift. The mechanism for continuity is one you build: persist state to the repo, and make reading it step 1 of the skill. That's why runs/<date>/state.json exists, why the skill reads the prior run before anything else, and why analysts receive last week's cohort section alongside this week's data. Memory across scheduled runs is an engineering artifact here, not a checkbox.

Wiring the schedule. Cloud routines are the managed path: run /schedule in Claude Code (or claude.ai/code/routines) and create a weekly routine, "Mondays 6am: run /churn-analysis". The routine executes in a cloud sandbox that clones your repo's default branch, loads the project's .claude/ directory and .mcp.json, runs autonomously, and pushes its commits on a claude/-prefixed branch. Practical consequences you must handle:

  • Secrets: the cloud session doesn't have your laptop's .env. Configure the routine's environment variables (service keys for the export API) in the routine settings.
  • Merging: decide whether a human merges the weekly claude/ branch or you enable direct push for this repo. For a report repo, direct push is defensible; decide explicitly.
  • Allowance: routine runs draw on your plan's usage like any session. A weekly 8-agent fan-out is modest; a daily one across many workflows adds up, so watch /usage trends.

The self-hosted alternatives: system cron on any machine running headless mode (claude -p "/churn-analysis" --output-format json with --allowedTools scoped to what the pipeline needs), or a GitHub Actions cron with the official claude-code-action, whose docs literally include a scheduled daily-report example. You gain full control of secrets and environment; you take on the babysitting (the machine being asleep Monday 6am is now your incident, and in Actions your data exports now download inside CI).

Alerting on the run itself: add a Stop hook in .claude/settings.json that posts to Slack when a session ends, so a failed Monday run doesn't fail silently until Wednesday.


Phase 7: Sharing with the team

A report living in a git repo is invisible to your PM team. Two mechanics:

Artifact publish. Claude Code can render report.md as a hosted web page on claude.ai. Ask it to publish the report as an artifact: first publish requires your approval, and subsequent publishes reuse the same URL, so "this week's churn report" is a stable link. Visibility is plan-dependent: on Team/Enterprise you can scope it to your organization, and viewers just sign in to claude.ai with no tooling on their side. One real limitation to plan around: artifact publishing works from the CLI and desktop app, not from every automated surface. In a fully autonomous setup, the publish step may be the one place a human (or a different delivery mechanism) stays in the loop.

Slack delivery. Wire a Slack MCP server or a plain webhook script, and make step 7 of the skill post the top-three findings with a link to the full page. The diff section ("changed since last week") is what makes this readable as a feed instead of a weekly wall of text.


Phase 8: Maintenance (this system is now yours)

What you own from here on:

  • Schema drift. Product ships a new event taxonomy, build_features.py silently degrades, clusters go mushy. Add a validation step to the skill: expected event types present, volumes within historical range; refuse to report otherwise.
  • Cohort drift. Clusters shift as the product changes. The state.json baseline comparison flags it; a human decides when to re-baseline and re-name cohorts.
  • Definition changes. Finance redefines "churned," and now CLAUDE.md, the feature script, and historical comparability all need coordinated updates. Version the definition in state.json so old reports stay interpretable.
  • Prompt drift. Agent and skill prompts need tuning as you learn what the critic misses. Treat them like code: PR review on .claude/ changes, because a prompt edit changes what your executives read.
  • Cost watch. /usage after runs; if spend creeps, the cause is almost always an agent that started free-exploring the parquet files instead of reading its summary.
  • Access & secrets. Export API keys rotate; the routine's env vars are one more place they live.

What this costs

Churn analysis is the cheap end of these workflows to run, because the expensive work happens in Python, not in tokens. Where the money actually goes:

  • Token spend per run. The weekly session is the skill overhead plus the fan-out: eight analysts each reading a 1-3K token cohort summary and running a handful of targeted queries, then a synthesizer and critic reading the drafts. This fits comfortably inside a Pro or Max subscription's included usage. The number to watch is not the average run but the bad run, where an agent starts reading parquet files directly instead of its summary. The --allowedTools scoping and the CLAUDE.md rules exist precisely to cap that.
  • Plan tier. Cloud routines and artifact publishing are subscription features (Pro/Max/Team/Enterprise), and organization-scoped artifact sharing specifically needs Team or Enterprise. If the report must reach teammates who aren't on your Claude plan, budget for seats or build the Slack delivery path instead.
  • The cost that doesn't show on any invoice. Someone specified the features, validated the clusters, tuned three agent prompts, wired the routine's secrets, and owns the maintenance duties above. That person is doing data engineering. Whether that's a good use of a PM's week is the real cost question, and only you can price it.

The four practical challenges

Every recurring analysis workflow you build on Claude Code hits the same four walls. Here is where each showed up in this playbook:

  1. Setup at scale: the pipeline-and-aggregates architecture (Phases 1-4), because 5M events cannot and should not go through a context window.
  2. Sharing with the team: artifact publishing plus Slack delivery (Phase 7), with the plan-tier and automation caveats that come with them.
  3. Maintenance: schema drift, cohort drift, definition changes, prompt drift (Phase 8). This is the part that never ends.
  4. Cost: modest per run if agents stay on rails, real if they don't, plus the engineering time that never appears in /usage.

The managed alternative

If you want this without building it, the adjacent products come at churn from two angles. Analytics platforms like Amplitude and Mixpanel ship retention and churn reporting out of the box: connect your events and you get cohort retention curves the same day. Customer success platforms like Gainsight and ChurnZero score account health and churn risk for CS teams. What you get is speed and a supported UI. What you give up: the diagnosis narrative (dashboards show you the curve; the "why" is still your job), behavioral clustering on their terms rather than your feature definitions, and another per-seat subscription that solves exactly one workflow.

Doing this with Second Axis

Everything above is the do-it-yourself path. On Second Axis, churn analysis is a workflow in the marketplace: you connect your analytics and warehouse, choose the workflow, and set the goal ("weekly cohort-level churn diagnosis, Mondays, to the product channel"). It runs on schedule from there, with the cohort clustering, analyst agents, and run-to-run memory built in, and it monitors itself: if a data source breaks, a run fails, or the numbers drift outside sanity checks, it finds the error and alerts you instead of failing silently.