Culprit
For mobile teams shipping behind feature flags

Know which flag broke your app, before anyone is paged.

Culprit compares crash, ANR and error rates for every feature-flag variant against its control group. When one is clearly worse, it tells your team in Slack exactly which variant, on which app version, and where. If you allow it, it also switches that variant off.

Read the story behind Culprit → · See the test results and demo →

CrashlyticsSentryFirebase Remote ConfigStatsigiOS + AndroidOpen source
Culprit
APP · 03:12
🚨 new_checkout=on on v5.2: crash rate 1.20% vs control 0.20% (120/10000 users, p=1.1e-17). Action: killed. Undo: turn killswitch_new_checkout off in Statsig. Concentrated in: Android 14 (62% vs 20% control) · Samsung SM-S918B (41% vs 9% control).

A real alert format. Nobody had to open a dashboard to find the cause.

Why

Mobile releases can't be rolled back. Flags can.

Once a build is in the store, a feature flag is the fastest lever you have. But crash tools tell you that crashes went up, not which of your dozen running flags and experiments caused it. Culprit takes over that investigation.

Today, at 3am

Someone pieces it together by hand

  1. Crash-free rate alert fires
  2. Open the crash tool, then the flag tool, then Slack history
  3. Guess which rollout overlaps the spike
  4. Check whether it's one app version or all
  5. Find the person who can flip the flag
With Culprit, ~15 minutes, no one paged

The answer arrives in Slack

  1. Every variant is compared with control, per app version
  2. The alert names the flag, variant, version and signal
  3. It shows where the errors concentrate (OS, device)
  4. Opted-in flags are switched to control automatically
  5. Every alert and kill is logged for the dashboard

Names the cause

Not "crashes are up". It reports "new_checkout=on on v5.2 crashes 6× more than control".

Crashes, ANRs and errors

Each signal is checked on its own, so a variant that freezes the app is caught even when crashes are flat.

Few false alarms

Minimum sample sizes, a 2× ratio, and a sequential test that stays valid however often the job checks, corrected for the number of flags.

Safe auto-kill

Alert-only by default. Kills are opt-in per flag, only ever send users to control, and are capped at one a day.

Fits your stack

Crashlytics or Sentry for crash data, and Firebase Remote Config or Statsig for flags, in any combination.

History you can see

Every finding goes to BigQuery, with a ready-made Grafana dashboard or a one-click Looker Studio report.

How it works

Three parts, no new servers to run

A small wrapper in the app, a scheduled job on Google Cloud, and a kill switch per watched flag.

Tag

The app reads watched flags through a ~30-line Flags wrapper. It tags every crash and session with ff_<flag>=<variant> and checks killswitch_<flag> first.

Compare

Every 15 minutes a Cloud Run job counts affected users and active users over the last 7 days, per flag, variant, app version and signal, and compares each variant with control.

Act

A clear regression gets a Slack alert with its OS and device breakdown. For opted-in flags, the job also turns the kill switch on, and the app serves control on its next fetch.

Try it

Run the detector yourself

Below are the same rules and statistics the real job uses. Change the numbers to see when Culprit raises an alert, and what it would post.

Control (off)
Variant (on)
Control rate
Variant rate

    The p-value is from a sequential test (mSPRT), which stays valid when the job checks again every 15 minutes as data builds up over 7 days. With N flags watched, the job makes 3 × N comparisons per app version, and each must beat 0.01 ÷ comparisons (Bonferroni correction).

    Setup

    Add Culprit to your project

    Pick your tools and platform. Every step and snippet below updates to match.

    Crash data
    Feature flags
    App platform

      Culprit is an early-stage open-source project. Its logic is covered by offline tests, so run it in a staging project and use --backtest before turning on auto-kill in production.

      Safety

      An automatic kill switch you can trust

      A tool that can turn features off needs strict limits. These are built in, not optional.

      Off by default

      A new install only sends alerts. Auto-kill needs two opt-ins: "auto_kill": true for the flag in a reviewed PR, and AUTO_KILL=1 on the job.

      Only ever toward safety

      The job can only turn killswitch_<flag> on, which sends users to the control path. Code checks refuse any other change, and tests cover them.

      Hard to trigger by accident or abuse

      Non-fatal errors never kill. Kills need at least 10 affected installs plus a significant result, and are capped at one a day.

      Least privilege

      Read-only access to crash data. Secrets live in Secret Manager. No service-account key files. The dashboards use read-only access.

      FAQ

      Questions teams ask

      Does it replace Crashlytics or Sentry?

      No. It reads from them. Your crash tool still collects and groups crashes; Culprit adds the "which flag variant caused it" answer and the kill switch.

      What does "active users" mean with Sentry?

      Sentry can't break its session counts down by custom tags, so Culprit counts distinct users on app-start traces, which carry the same flag tags. That means sampling app-start traces at 100%.

      What happens if the breakdown query fails?

      The alert still goes out, with "Breakdown unavailable". The OS and device breakdown is extra context, and it never delays a kill or blocks an alert.

      How do I undo a kill?

      Turn killswitch_<flag> back off in Remote Config or Statsig. Both keep a change history. Find the cause first: the detector will alert again if the rate climbs back.

      Where does it run, and what does it need?

      A Cloud Run job triggered every 15 minutes by Cloud Scheduler, with a BigQuery table for the audit log. One deploy.sh sets everything up.