Why Culprit
When a database gets slow, someone has to dig through Query Store, wait statistics, plan caches and execution plans to find out why. That takes an expert, an afternoon and a handful of tools that each show a piece of the picture.
Culprit does the digging in seconds. It reads the statistics your database already keeps, works out where the time goes, and tells you in one sentence what is wrong, what it costs and what to change:
Repeats its read of
order_items1.5M times, once per row oforder_items. 27% of database time, and it times out on every run · Fix: the query forces a loop join; remove the hint and let the optimizer choose · Verify: run it with actual rows: a hash join, or far fewer executions oforder_items.
🎯 Answers, not raw numbers
Every finding is ranked by its share of database time and comes with the evidence, a production-safe fix and a way to prove it worked.
🛡️ Safe on production
Scans read statistics only and never run your queries. Anything that would change data on a production database asks first.
🤖 Built for coding agents
An MCP server, JSON output everywhere and per-connection permissions let Claude Code, Cursor or Codex investigate with you. Every call is approved and audited.
⚡ Fast and light
A 14 MB app that opens in under a second and shows the last report instantly while a fresh scan runs in the background.
🧭 One workflow across your databases
The same views, wording and workflow on SQL Server, PostgreSQL, TimescaleDB, YugabyteDB, MySQL, MariaDB and SQLite. When an engine can't show something, Culprit says why in one sentence.
🔌 Open and pluggable
Add your own checks or database engines without forking. The app, the CLI and the agent tools share one Rust core.
Coding agents and CI
Culprit is designed to be driven by a coding agent or a CI script as comfortably as by a person. Every CLI command prints JSON when piped (or SARIF 2.1.0 with --format sarif), and finding ids are stable across runs.
Coding agents (MCP)
culprit mcp serves 27 tools over the Model Context Protocol: dashboard, findings, plans, plan insights, migration linting, schema drift, what changed, blocking, waits, index advice, verification and more. When queries carry file:line comments (Marginalia, SqlCommenter or /* app/models/order.rb:42 */), each finding's brief points straight to the call site in your repository.
claude mcp add culprit -- culprit mcp # Claude Code; Cursor and Codex take the same command
culprit agent init # teach agents in this repository to use Culprit
From the terminal, you can also print a finding's brief directly or test a proposed index without building it where the provider supports hypopg:
culprit show shop 'filtered-scan:public.orders'
culprit explain shop --sql "SELECT * FROM orders WHERE status = 'open'" \
--with-index "CREATE INDEX ON orders (status ASC)"
You decide what agents may do, per connection: nothing, read statistics and plans (the default), or run read-only queries that you approve one by one. Approval shows the exact statement and what Culprit guarantees about it, and every tool call lands in a local audit log.
Pull requests and deploy pipelines
culprit lint-sql checks migration files offline against a saved snapshot, using the database's actual table sizes, column types, indexes and active workload to catch blocking CREATE INDEX, DROP INDEX and ALTER TABLE locks, unindexed or type-mismatched foreign keys, and DROP or RENAME changes that break live queries:
culprit lint-sql shop --file migrations/ --fail-on high # exit 3 on high-severity findings
culprit lint-sql shop --file migrations/ --write # add CONCURRENTLY / ONLINE = ON / LOCK=NONE in place
culprit lint-sql shop --file migrations/ --format sarif > db.sarif # SARIF 2.1.0 for GitHub Code Scanning
# .github/workflows/db-check.yml
- name: Lint migrations against production snapshot
run: culprit lint-sql prod-snapshot.json --file migrations/ --format sarif > culprit.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: culprit.sarif
After a deploy, mark the timeline so regressions line up with the release, and re-scan to verify that a fix worked:
culprit mark shop deploy v2.4.0 # put the deploy on the database timeline
culprit verify shop 'filtered-scan:public.orders' # exits 2 if the finding is still present
culprit digest shop > digest.json # redacted JSON summary for Slack or Teams
What you get
Findings you can act on
68 checks look for what actually costs time: runaway loops in plans, plan regressions, missing and unused indexes, N+1 query loops, type mismatches that block indexes, parameter-sensitive plans, queries that fail or time out, spills to disk, hot rows, statements that lock a whole table, unindexed foreign keys, sequence and transaction-ID exhaustion, partition hazards, page checksum corruption, slow storage, connection storms, replication lag, stale statistics, risky settings and more. Each finding names the queries involved, shows their share of database time, and ends with a fix you copy (Culprit never applies it for you) and a way to verify it.
Plans that explain themselves
Every captured plan comes with insights in plain words ("repeats its read of order_items 1.5M times, once per row"), a diagram that shows where the cost goes, and the operator's details one click away. 35 rules cover SQL Server, PostgreSQL (including TimescaleDB), YugabyteDB, MySQL, MariaDB and SQLite plans. Measure a run safely (writes are rolled back), and on SQL Server see the last actual plan without re-running the query or watch a long query's progress step by step.
"What changed?" in one drag
Drag across the database-time chart to compare two periods. Culprit lists what got slower, what's new and what improved, and says why: "Got 293× slower at 5:00 PM, when SQL Server switched from plan 163 to plan 198." It also lines regressions up with everything else that changed around them: a dropped index, a new setting, a restart, a deploy your pipeline marked with culprit mark, or the cloud throttling the server (Amazon RDS CPU credits running out, Azure tier limits), read with your own aws or az sign-in.
Compare any two plans
Paste or drop a plan from anywhere (SSMS .sqlplan, PostgreSQL EXPLAIN, YugabyteDB EXPLAIN (ANALYZE, DIST, FORMAT JSON), MySQL EXPLAIN FORMAT=JSON) and compare it with another. The headline says what matters: "Plan 13 is estimated 3.5K× more expensive because it scans orders where Plan 12 seeks orders_status_created_idx." Every changed step is marked in both diagrams.
Who is blocking whom, right now
Live sessions turn a wall of lock waits into one sentence and a tree: which session holds everyone up, why (here: a transaction left open by the application) and who waits behind it. Deadlocks get the same treatment, with the pattern named and the fix spelled out.
And everything else, done properly
Safe on production
- Read-only by design. Scans read statistics and catalog views only, with short lock timeouts. Estimated plans never execute anything.
- Planning never lends your privileges. On PostgreSQL and YugabyteDB, planning can invoke functions. Elevated logins plan only their own recorded statements, and none while functions other roles wrote are in reach. Every plan is made in a read-only transaction that is rolled back.
- Production is guarded. Statements Culprit can prove only read run in a read-only transaction. Anything else on a production connection needs an explicit confirmation that says what it will do, and destructive statements need the connection's name typed.
- Fixes are suggestions. Every fix is shown and copied, never applied, and uses online or concurrent operations where the server supports them.
- Secrets stay put. Passwords live in the OS keychain, Entra ID uses your
az login, and nothing about your servers leaves your machine. Exports redact literal values by default. - Agents get least privilege. Off, statistics only, or approved read-only queries, per connection, with every call audited.
Supported databases
See Engine parity for the feature comparison and each provider's setup, measurements and verified versions.
When a source is switched off (Query Store, pg_stat_statements, performance_schema), Culprit shows the one statement that turns it on and what you'll get once it's on.
How it works
Sources ──► Snapshot ──► Prepare ──► Detect (68 checks) ──► Refine ──► Rank ──► Views
- Offline-first snapshots (
culprit-core). Collectors read catalog and statistics views into a self-containedSnapshot. Every check, view, diff and migration lint is a pure function of that snapshot—replayable offline in CI or on another machine without a database connection. - Evidence rules, not guesses (
culprit-checks). Checks never flag a query from a plan's shape alone when the server has runtime counters. Rates require at least an hour of counter history, "unused" index claims require a week, unavailable sources lock their dependent checks instead of reporting zero issues, and the refinement pass merges related symptoms into one root-cause finding. - One declarative spec per engine (
EngineSpec). Capabilities, SQL dialect rules, setting thresholds and refusals live in one spec per engine (crates/culprit-core/src/engines/), shared by the Rust analyzer and the TypeScript UI. - Embeddable Rust workspace. The core model (
culprit-core), analyzer (culprit-checks), workspace and secrets engine (culprit-engine), query-log parser (culprit-querylog), MCP server (culprit-mcp), CLI (culprit-cli) and engine providers (culprit-postgres,culprit-mssql,culprit-mysql,culprit-sqlite,culprit-yugabyte) can be embedded as a library, with customChecks or runtime providers plugged in without forking.
Get started
Download the desktop app (.dmg for macOS, installer for Windows, .AppImage or .deb for Linux) and the standalone CLI from the latest release. The desktop app checks for a new release once a day and offers it at the foot of the sidebar; it installs only an update signed with Culprit's key, and restarts when you choose (turn the daily check off from the command palette). A downloaded CLI keeps itself current with culprit update, which replaces it with the latest release after checking its SHA-256; the CLI never checks on its own.
To build from source instead, you need Rust (stable) and Bun; on Linux, also the WebKitGTK development packages listed in the guide.
git clone https://github.com/cristipufu/culprit-sql-studio.git
cd culprit-sql-studio
cargo build --release -p culprit-cli # the culprit CLI, in target/release
cd app && bun install && bunx tauri build # the desktop app
Connect a database and get its dashboard:
culprit conn add shop --env staging --url 'postgres://[email protected]:5432/shop'
culprit dashboard shop # the database at a glance
culprit scan shop # every finding, ranked
Or open the desktop app, pick the engine and paste its connection string (or choose the SQLite file), and the first report appears in seconds.
Just looking? cd app && bun install && bun run dev opens the full interface in your browser with demo data: no database needed.
Learn more
- Guide: every check and command, the agent setup, measured speed, safety details and how to extend Culprit
- Architecture: the analysis pipeline, the evidence rules every check follows and the crate map
- Design: how the product behaves and why
- Coding agents: MCP tools, permissions, approvals and the audit log
- Engine parity: feature comparison, provider guides, setup and verified versions
Culprit is open source under the MIT license.