Your coding agent is one ALTER TABLE away from a production outage. It will happily
write CREATE INDEX and run it on a 10M-row table — and lock writes for minutes.
db-migration-safe is the free guardrail that lets you let an agent near your database: it
catches the lock hazard, rewrites the migration to zero-downtime, generates the rollback,
and refuses to run anything risky without you (EXECUTE never auto-fires).
An open-source Claude/Agent Skill that makes SQL schema migrations safe. It detects
locking and blocking hazards, rewrites unsafe DDL into zero-downtime multi-step
migrations, auto-generates rollbacks, and gates execution behind a
plan → validate → execute workflow.
- Postgres-first — strong and deterministic (static lint → safe rewrite → real-lock trace → rollback → gated execute).
- MySQL — an honest hybrid (InnoDB matrix heuristics + execute-online tooling), materially weaker, and labeled as such.
- Engine-agnostic — works on hand-written
.sqlor any ORM's generated SQL. - Free / Apache-2.0 — it wraps existing open-source linters; it does not rebuild them.
An agent writes a bare
CREATE INDEXon a large table;analyze.pyflags the write-blocking lock (squawk:require-concurrent-index-creation+eugene:E6/E9) and theCONCURRENTLYrewrite re-lints clean.PLAN/VALIDATEtouch no database.
The migration-safety space splits into two halves that do not overlap:
- Shallow pattern-skills that describe expand-contract in prose but do zero pre-flight lock analysis, no rollback generation, no tool integration.
- Excellent standalone lock linters that no skill wraps —
squawk,eugene,gh-ost/pt-osc.
The one serious competitor, Atlas Agent Skills, is engine-locked and paywalls the
rules that matter: as of Atlas v0.38 (2025-10-28), every Postgres concurrency/lock
analyzer (PG101–105, PG301–311) and the MySQL blocking rules (MY130–136) are Pro.
No open-source skill orchestrates squawk + eugene (or the MySQL equivalents) into a
plan→validate→execute loop with size-aware lock reasoning and auto-generated rollbacks.
This fills that gap.
| Atlas Agent Skills | squawk / eugene alone | db-migration-safe | |
|---|---|---|---|
| Lock-hazard analysis | paywalled (Pro) | yes, but unwrapped | wraps squawk + eugene lint + trace, one verdict |
| Engine lock-in | requires Atlas engine | none | none — raw .sql or any ORM |
| "100 rows vs 100k" | — | trace only, manual |
trace on an ephemeral DB, orchestrated |
| Safe rewrite | describes | warns | emits the concrete multi-step rewrite |
| Rollback generation | up/down convention | none | generated from the DDL; flags irreversible ops |
| Cost | paid lock rules | free | free / OSS |
Does it actually work? Measured across Haiku 4.5 / Sonnet 4.6 / Opus 4.8 over three rounds
— common operations, then subtle traps (FK NOT VALID, UNIQUE concurrently,
volatile-default rewrite, direct SET NOT NULL), then partitioned indexes: with the
skill, every model produced a machine-verified (0-error) safe rewrite plus a generated
rollback on every task. Unaided, the frontier models already write safe DDL on the first two
rounds — the skill's baseline catch there is concentrated on the cheaper, faster model (Haiku
1/3 and 2/4 → clean). Round 3 is the exception that reaches a frontier model:
CREATE/DROP INDEX CONCURRENTLY is silently unsupported on a partitioned parent, so Sonnet
(and Haiku) reach for it and fail at deploy while static linters wave it through — only Opus
gets it right unaided, and the skill's is_partitioned.sql probe + catalog #12 lift Sonnet
and Haiku to safe. Full method and tables in evals/eval.md.
This is a Claude Code skill. Drop it where your skills live:
git clone https://github.com/alexbobkovv/db-migration-safe ~/.claude/skills/db-migration-safeIt works install-free: with no binaries present (or analyze.py --no-external), the
Postgres path falls back to a stdlib heuristic over the cataloged ops so PLAN/CI still
flag obviously-unsafe DDL — clearly labeled non-authoritative. Install the binaries it
shells out to for authoritative analysis and the VALIDATE gate (only what you need) — see
references/tool-setup.md:
npm install -g squawk-cli # or pip install squawk-cli
mise use ubi:kaaveland/eugene@latestThe skill shells out to squawk/eugene/gh-ost as separate processes — it does
not bundle them, which keeps it lightweight and license-clean.
# 1. PLAN — static analysis (no DB)
python3 scripts/analyze.py migration.sql --dialect postgres
# 2. rewrite the flagged statements per references/postgres-catalog.md → migration.safe.sql
# 3. VALIDATE — re-lint until zero error-level findings
python3 scripts/analyze.py migration.safe.sql --dialect postgres
# 4. ROLLBACK — generate the reverse migration
python3 scripts/gen_rollback.py migration.safe.sql > migration.rollback.sql
# 5. TRACE (optional) — observe real locks on an ephemeral Postgres
python3 scripts/trace.py migration.safe.sql
# 6. EXECUTE — user-triggered only; apply with lock_timeout + CONCURRENTLY (see SKILL.md)| Phase | What it does | Touches your DB? |
|---|---|---|
| PLAN | squawk + eugene lint → merged verdict; size probe; optional trace |
no |
| VALIDATE | re-lint the rewrite; gate on zero error-level findings | no |
| EXECUTE | apply with lock_timeout, CONCURRENTLY, batched backfill |
yes — user-triggered only |
PLAN/VALIDATE are analysis only and safe to auto-run. EXECUTE never auto-fires —
it runs only when you explicitly ask to apply the migration (see the Safety contract in
SKILL.md).
analyze.py exits nonzero on any error-level finding, so it drops straight into CI. The
easiest path is the bundled GitHub Action — it analyzes the migrations changed in a PR
and uploads SARIF so findings annotate the diff inline (and land in the Security tab):
# .github/workflows/migrations.yml
name: migration-safety
on: pull_request
permissions:
contents: read
security-events: write # required to upload SARIF to code scanning
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # so the Action can diff against the PR base
- uses: alexbobkovv/db-migration-safe@v1
with:
dialect: postgres # or mysql
# files: db/migrate/*.sql # optional: override the changed-files defaultIt installs squawk for authoritative analysis and falls back to the stdlib heuristic if
that install fails, so the gate never silently no-ops. Prefer to wire it by hand? The
scripts are a plain CLI:
- run: pip install squawk-cli # optional; heuristic fallback works without it
- run: |
for f in $(git diff --name-only origin/${{ github.base_ref }}... -- '*.sql'); do
python3 scripts/migrate_safe.py analyze "$f" --dialect postgres || exit 1
doneBlock unsafe DDL before it's even committed. Add to .pre-commit-config.yaml:
repos:
- repo: https://github.com/alexbobkovv/db-migration-safe
rev: v0.2.0
hooks:
- id: db-migration-safe # runs on changed *.sql; postgres by default
# args: [--dialect, mysql]It runs install-free via the heuristic, and uses squawk/eugene automatically when they
are on your PATH.
The installed version is in VERSION, and every script reports it:
python3 scripts/analyze.py --versionIt is a skill you install by cloning, so you upgrade by pulling:
git -C ~/.claude/skills/db-migration-safe pullCHANGELOG.md records what changed. The project follows semver for its
public surface — the scripts/* flags and exit codes, and the rule ids and rewrite
shapes under references/. A change to any of those is breaking (major); new safe
behavior is a minor bump; fixes are patches.
SKILL.md entry point (workflow + safety contract)
references/ postgres-catalog · mysql-catalog · squawk-rules · eugene-hints · tool-setup
scripts/ migrate_safe.py (dispatcher) · analyze.py · trace.py · gen_rollback.py · table_size.sql · is_partitioned.sql (stdlib only)
evals/ baseline cases + methodology
CHANGELOG.md what changed per version
CONTRIBUTING.md dev setup · eval workflow · invariants
Issues and PRs welcome — especially "the verdict was wrong" reports (a false positive, or
a hazard the tool missed), which is how the catalogs improve. The scripts are
standard-library-only by design; please keep new code dependency-free. See
CONTRIBUTING.md for the dev setup, how to run the evals, and the
invariants to preserve.
Apache-2.0. The skill invokes third-party binaries (squawk, eugene, gh-ost, …) as
separate processes; it does not bundle or statically link them, so their licenses are
independent. Install them yourself per references/tool-setup.md.

