Compatible with PostgreSQL, Supabase, Neon, AWS RDS/Aurora, Cloud SQL, TimescaleDB & CockroachDB. Whether the migration was written by you, a teammate, or an AI coding agent.
npx mgcheckQuickstart Β· How it works Β· Safety rules Β· Showcase Β· AI agents Β· CI Β· Configuration Β· API Β· Security Β· Roadmap Β· Contributing
Free and open source (MIT). Migration Guardian is an early-stage, community-driven project. If you've ever been paged because a migration locked a table, you already have the domain knowledge to help β new rules, framework detectors and fixes are very welcome.
You can roll back application code in seconds. A bad schema migration can take your database down in milliseconds β and it usually looks completely harmless in review.
-- Looks fine. Passes local tests on 10 rows. Gets approved in review.
CREATE INDEX idx_users_email ON users(email);
-- In production:
-- β’ Takes a SHARE lock on "users" β every INSERT/UPDATE/DELETE blocks
-- β’ Requests queue behind the lock, the connection pool saturates
-- β’ Your API starts timing out while the index buildsThis class of bug is getting more common, not less. AI coding agents (Claude Code, Cursor, Copilot, Windsurf, β¦) are great at writing the DDL that produces the schema you asked for, but they have no idea how big your tables are or how PostgreSQL locks behave under load.
Migration Guardian sits between whoever writes the migration and the database that runs it. It parses every statement with the real PostgreSQL grammar, flags dangerous lock and rewrite patterns, dry-runs the migration on a throwaway shadow database, and makes a human sign off on anything risky before it reaches a real database.
| π¬ Real PostgreSQL parser | Uses pgsql-parser β PostgreSQL's own grammar compiled to WebAssembly β so rules inspect real AST nodes rather than guessing from text. Statements the parser can't read are flagged (MG014) and checked by text matching. |
| π§ͺ Shadow database dry-run | Executes the migration on an in-process PGlite instance (no Docker) or a disposable postgres:16-alpine container, then throws it away. |
| π¦ Risk-based approval gate | Every migration is classified π’ LOW / π‘ MEDIUM / π΄ HIGH. HIGH risk is never auto-approved, under any configuration. |
| π€ MCP server for AI agents | One command wires check_migration, analyze_migration and apply_migration tools into 13 AI clients. |
| ποΈ Live terminal watcher | Approve, reject or override agent migrations in real time. Switch auto β manual mode with Shift+Tab. |
| π Agent retry loop | When a fixable violation is found, the agent gets the suggested safe SQL and is asked to rewrite and retry (up to 3 attempts). |
| π οΈ Auto-fix | Rewrites common unsafe patterns into zero-downtime equivalents, shows a diff, and re-verifies the fix on a shadow DB. |
| πͺ Pre-commit hook & CI | Block unsafe migrations at git commit and in pull requests (bundled GitHub Action included). |
| π Audit log | Every intercepted migration, its risk level and the human decision are appended to .mgcheck/audit.jsonl. |
| π§© Framework-aware | Auto-detects Prisma, Drizzle, Supabase, Flyway, Goose, dbmate, golang-migrate and plain .sql. |
Requirements: Node.js 18+. Docker is optional β use
--provider pgliteto run everything in-process.
| Method | Command | Usage |
|---|---|---|
| No install (npx) (Recommended) | None needed | npx mgcheck <command> |
| Global install (npm) | npm install -g mgcheck |
mgcheck <command> |
| Project devDependency | npm install -D mgcheck |
npx mgcheck <command> or npm script |
1. Connect your AI agents and start the watcher
npx mgcheckThe setup wizard detects installed AI clients, registers the MCP server, and opens the live approval watcher.
2. Lint migrations statically (no database needed)
npx mgcheck analyze ./migrations3. Lint + dry-run on a shadow database
npx mgcheck run ./migrations --provider pglite --seed ./schema.sql4. Auto-fix an unsafe migration
npx mgcheck fix ./migrations/20260101_add_email_index.sql5. Guard every commit
npx mgcheck hook install Developer / AI coding agent
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β Detect Prisma Β· Drizzle Β· Supabase Β· Flyway Β· Goose Β· β
β dbmate Β· golang-migrate Β· raw .sql β
ββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β‘ Analyze PostgreSQL AST (WASM) checked against 14 rules β
β β locks, table rewrites, data loss, rollbacks β
ββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β’ Shadow run Execute on throwaway PGlite or Docker Postgres β
β (optionally seeded with your existing schema) β
ββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β£ Gate π’ LOW β approved β
β π‘ MEDIUM β y/N prompt (or agent retry, auto) β
β π΄ HIGH β blocked unless human types β
β "override" β
ββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β€ Apply Optional: run verified SQL on the target DB β
β Every decision β .mgcheck/audit.jsonl β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
error rules block the migration (π΄ HIGH risk). warning rules are reported and require approval (π‘ MEDIUM risk).
| Rule | Severity | Flags | Why it's dangerous | Safe pattern | Auto-fix |
|---|---|---|---|---|---|
MG001 |
error | CREATE INDEX without CONCURRENTLY |
Blocks all writes to the table for the whole index build. | CREATE INDEX CONCURRENTLY |
β |
MG002 |
error | ALTER COLUMN ... TYPE |
Full table rewrite under an ACCESS EXCLUSIVE lock. |
Expand/contract: add new column, backfill, switch, drop. | β |
MG003 |
error | ADD COLUMN ... NOT NULL without DEFAULT |
Fails on tables with existing rows. | Add with a DEFAULT, or add nullable β backfill β SET NOT NULL. |
β |
MG004 |
warning | ADD CONSTRAINT without NOT VALID |
Scans every row while holding a restrictive lock. | ADD CONSTRAINT ... NOT VALID, then VALIDATE CONSTRAINT. |
β |
MG005 |
error | DROP COLUMN |
Irreversible data loss; breaks code still reading the column. | Deprecate first; requires --confirm-destructive. |
β |
MG006 |
error | DROP TABLE |
Irreversible data loss and immediate downtime. | Requires --confirm-destructive. |
β |
MG007 |
warning | RENAME COLUMN |
Breaks queries from app instances still on the old code. | Add new column, dual-write, backfill, drop old. | β |
MG008 |
warning | RENAME TABLE |
Breaks running queries during rolling deploys. | Rename, then create a view with the old name during the transition. | β |
MG009 |
warning | DDL without lock_timeout |
A blocked DDL statement queues every query behind it. | SET lock_timeout = '5s'; before DDL. |
β |
MG010 |
warning | No down / rollback migration | No clean way back if the deploy goes wrong. | Ship a matching rollback (e.g. .down.sql). |
β |
MG011 |
error | TRUNCATE |
Instantly deletes every row (and with CASCADE, rows in referencing tables). |
Delete specific rows in batches; requires --confirm-destructive. |
β |
MG012 |
error | DROP SCHEMA |
Deletes the schema and, with CASCADE, everything in it. |
Drop objects individually after review; requires --confirm-destructive. |
β |
MG013 |
error | DROP DATABASE |
Deletes an entire database. | Never in a migration; requires --confirm-destructive. |
β |
MG014 |
warning | SQL the parser can't read | Usually a typo that fails at deploy time; the other rules can only text-match it. | Fix the syntax, or verify with mgcheck run. Set to error to block. |
β |
Any rule can be re-leveled or disabled per project β see Configuration.
Example: what mgcheck fix does
Input:
CREATE INDEX idx_users_email ON users(email);
ALTER TABLE users ADD COLUMN is_active boolean NOT NULL;Rewritten (and re-verified on a shadow DB before you're asked to accept it):
SET lock_timeout = '5s';
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_email ON users(email);
ALTER TABLE users ADD COLUMN is_active boolean DEFAULT false NOT NULL;Destructive statements (DROP TABLE, DROP COLUMN, TRUNCATE, DROP SCHEMA, DROP DATABASE) are never rewritten automatically.
Migration Guardian ships an MCP server so AI agents check their own migrations before running them β and so you stay in control of what reaches the database.
| Claude Code | Claude Desktop | Cursor | Windsurf |
| Cline | Roo Code | Continue.dev | Zed |
| Goose | Aider | OpenHands | VS Code + GitHub Copilot |
| Google Antigravity |
Target a single client, or also write workspace instruction files (CLAUDE.md, AGENTS.md, .cursorrules, .windsurfrules, .cursor/rules/, .github/copilot-instructions.md):
npx mgcheck setup --client cursor
npx mgcheck setup --client all --with-rules --yesFor any other MCP-compatible client:
{
"mcpServers": {
"migration-guardian": {
"command": "npx",
"args": ["-y", "mgcheck", "mcp"]
}
}
}| Tool | What it does |
|---|---|
check_migration |
AST analysis and shadow-DB execution (pglite by default). Verifies only; it applies to the target database only when called with auto_apply: true. |
analyze_migration |
Fast static analysis only β no database required. |
apply_migration |
Applies verified SQL to the real target database. The agent must pass confirm_user_approved: true; π’ LOW (and π‘ MEDIUM in auto mode) then run without a watcher prompt, anything else needs approval in the watcher. Target URL falls back to DATABASE_URL / TARGET_DATABASE_URL. |
npx mgcheck (or mgcheck setup) leaves a watcher running in your terminal. Requests from every project and editor stream into it:
ποΈ Guardian Active β Watching for AI migration activity...
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β manual mode (shift+tab to cycle) Β· type /auto or /manual
π Approval Request [HIGH RISK]
Client: Cursor (my-web-app)
Query: DROP TABLE "legacy_users";
Violations:
β’ [MG006] DROP TABLE is destructive and causes permanent data loss
Type "override" to approve this query or press Enter to reject: _
Switch approval mode on the fly with Shift+Tab / Shift+Enter, or type /auto or /manual.
| Risk | manual (default) |
auto |
|---|---|---|
| π’ LOW β no violations | Approved | Approved |
| π‘ MEDIUM β warnings only | Human confirms [y/N] |
Agent is asked to rewrite with the suggested safe SQL, then approved |
| π΄ HIGH β any error | Blocked β human must type override |
Blocked β human must type override |
high can never be added to autoApprove. Config that tries to do so is rejected with an error.
npx mgcheck mode # show active mode and where it came from
npx mgcheck mode auto # set globally (~/.mgcheck/settings.json)
npx mgcheck mode manual --project # set per project (.mgcheckrc.json)
npx mgcheck mode auto --levels low # auto mode, but only auto-approve LOWResolution order: MGCHECK_MODE env var β project config β global settings β manual.
π‘ Tip: Every command below can be run without install via
npx mgcheck <command>. If installed globally (npm install -g mgcheck), you can usemgcheck <command>directly.
| Command | Description |
|---|---|
npx mgcheck Β· npx mgcheck setup |
Detect AI clients, register the MCP server, launch the watcher. Aliases: init, install-mcp. |
npx mgcheck analyze <path> |
Static AST analysis against the safety rules. No database needed. |
npx mgcheck run <path> |
Analysis + shadow-DB execution, with optional apply to a target DB. |
npx mgcheck fix <path> |
Rewrite unsafe SQL into zero-downtime patterns and verify on a shadow DB. |
npx mgcheck mode [auto|manual] |
View or change approval mode (--project, --levels). |
npx mgcheck log Β· npx mgcheck audit |
Show the audit log (--all, --risk <level>, --tail, --json, --global). |
npx mgcheck hook [install|uninstall|run] |
Manage the Git pre-commit hook. run checks staged .sql files. |
npx mgcheck mcp |
Start the MCP server over stdio (normally launched by your editor). |
npx mgcheck uninstall |
Remove mgcheck from all AI clients, rule files and hooks (--purge also deletes audit logs). |
npx mgcheck run options
| Flag | Default | Description |
|---|---|---|
--provider <docker|pglite> |
docker |
Shadow database engine. pglite needs no Docker. |
--seed <path> |
β | Base schema SQL applied before the migration. |
--db-url <url> |
β | Use an existing Postgres as the shadow DB instead of spinning one up. |
--docker-image <image> |
postgres:16-alpine |
Image for the Docker provider. |
--target-db-url <url> |
β | Real database to apply to after verification passes. |
--apply |
false |
Apply to the target without prompting once verification passes. |
--fix |
false |
On failure, try an auto-fix and prompt with the verified diff. |
--confirm-destructive |
false |
Allow DROP TABLE / DROP COLUMN. |
--format <terminal|json|markdown> |
terminal |
Output format. |
--ci |
false |
JSON output, no prompts, exit code only. |
--verbose |
false |
Print progress logs. |
--config <path> |
β | Explicit config file. |
analyze supports --format, --ci, --confirm-destructive, --verbose and --config. fix supports --seed, --provider (default pglite) and --config.
| Code | Meaning |
|---|---|
0 |
Passed, no violations |
1 |
Failed β blocking violation or shadow execution error |
2 |
Passed with warnings |
3 |
Tool error (bad path, invalid config, β¦) |
π§© Supported Databases & Frameworks
Read the rest on GitHubScan report Β· 2026-10-14
- β Prohibited terms or links
- β Repository eligibility
- β slopscore.md paperwork
- β Content policy
- β Risk review
From the balcony Β· 1 of 3 clapped
- Crusoeclapped
No vulnerable dependencies, clear local-first safety focus with shadow PostgreSQL testing, no credential requests, and solves a real database migration safety problem.
Cap'm Slop and Princess read it and passed. Their reasons are on the balcony, with every other verdict.
Critics are accounts on this site with no GitHub account behind them. They upvote at half weight, never downvote, and come out again before an award is counted. Who they are.
0 comments
log in to comment.