/safe-migration — a schema change that can't silently break prod
A migration directory that has already been damaged once punishes the same
mistakes forever: a reused prefix, a db:generate that quietly emits a DROP,
a hand-written migration whose schema edit was forgotten, a db:push against
prod that skips the files entirely. Each of those has cost real downtime. This
skill is the fixed ritual that makes those failures impossible to reach by
accident. It is deliberately boring — the boring path is the one that ships.
It reads the host repo's own rules first (step 1) so it stays correct as the
project's conventions move, rather than hardcoding them.
When to use this
- Any change to a Drizzle schema file, or the user says "generate a migration",
"add a column / table / index", "db:generate", "alter the schema".
- Before shipping a branch that touches schema — as the pre-flight check.
When NOT to use this
- Read-only query changes, seed scripts, or data backfills that touch no schema.
- A repo with a healthy, linear migration history and CI that enforces it — the
full ritual is overhead there. Use it where a mistake is expensive.
- Applying migrations to production — this skill prepares and verifies; it
does not run
db:migrate against a shared/prod database. That is a human,
deliberate, out-of-band step.
The four failures this prevents
- Reused prefix — a new
0034_… when 0034 already exists. Derive the
next number from what's on disk, never from memory.
- Silent destructive SQL —
db:generate emitting DROP COLUMN/DROP TABLE
because a rename or removed field read as a deletion. Every generated file is
grepped for destructive ops and shown to the user before it's kept.
- Schema/migration drift — a hand-written
ALTER … DROP with no matching
edit to the schema file (the incident that broke prod for weeks). Schema and
migration must land in the same commit; the skill refuses to finish
otherwise.
db:push to a shared DB — skips the migration files and drifts schema vs
DB. Never run against anything but a local dev database.
Workflow
Load the repo's rules. Read the migrations directory's own README (search
**/migrations/README.md) and the root CLAUDE.md/AGENTS.md migration
section. They are the source of truth for prefix scheme, the check command,
and repo-specific hazards. Where they differ from this file, they win.
Find the next free prefix — from disk, not memory:
ls <migrations-dir>/*.sql | sed 's#.*/##' | sort | tail -5
Next prefix is lastNumber + 1, zero-padded to the repo's width. If the
directory has historical duplicate prefixes, tolerate them but do not add a
new collision — scan before naming.
Edit the schema file(s) for the actual change, matching the file's
existing conventions (read them; do not assume).
Generate:
<pkg-manager> db:generate # e.g. pnpm db:generate -> drizzle-kit generate
Then find the file it wrote (newest .sql).
Grep the generated SQL for destructive operations and SHOW the user:
grep -niE 'drop (table|column|constraint|index|type)|truncate|alter .*drop|delete from' <new.sql>
Any hit is a stop-and-confirm. A DROP is only correct when the user
genuinely intends to remove something; a DROP that surprises you is the
db:generate hazard firing — do not keep the file, fix the schema and
regenerate. See references/hazards.md.
Harden hand-written SQL. If you (not the generator) wrote any SQL, make
every statement idempotent — IF NOT EXISTS / IF EXISTS — and confirm the
schema file carries the matching change in this same working tree.
Run the repo's migration check (use what step 1 found, commonly):
<pkg-manager> db:check-migrations # or drizzle-kit check
Historical-dupe / journal-drift warnings are expected on a damaged
directory and are fine; a failure is not.
Stage schema + migration together, in one commit. List the staged files
back to the user and confirm both are present before committing. Never commit
a migration without its schema change, or vice versa.
The prod boundary (state it every time)
- Never
db:push / db:migrate against staging or prod from this skill.
- A migration must be applied to prod before the code that depends on it
deploys, or the new column/table 500s the live app. Say this in the report;
the apply itself is the user's deliberate step.
- If asked to check prod schema drift before shipping, do it read-only and
say plainly you are only reading.
Report format
- The change, the new migration filename, and the exact prefix reasoning.
- The destructive-op grep result — "none" is a result worth stating.
- Confirmation that schema + migration are staged together.
- The prod-apply reminder, and whether the check passed (warnings vs failures).
References
references/hazards.md — the real incident shapes (reused prefix, silent
DROP, schema drift, db:push) and how each is caught here.
1---2name: safe-migration3description: Run the safe Drizzle migration ritual for a repo with a fragile migration directory: pick the next free prefix, generate, grep the SQL for destructive operations, enforce IF EXISTS, run the migration check, and confirm schema lands with its migration in one commit. Use when the user changes a Drizzle schema file, says 'generate a migration', 'add a column/table', 'db:generate', or is about to ship a schema change.4---56# /safe-migration — a schema change that can't silently break prod78A migration directory that has already been damaged once punishes the same9mistakes forever: a reused prefix, a `db:generate` that quietly emits a `DROP`,10a hand-written migration whose schema edit was forgotten, a `db:push` against11prod that skips the files entirely. Each of those has cost real downtime. This12skill is the fixed ritual that makes those failures impossible to reach by13accident. It is deliberately boring — the boring path is the one that ships.1415It reads the host repo's own rules first (step 1) so it stays correct as the16project's conventions move, rather than hardcoding them.1718## When to use this1920- Any change to a Drizzle schema file, or the user says "generate a migration",21 "add a column / table / index", "db:generate", "alter the schema".22- Before shipping a branch that touches schema — as the pre-flight check.2324## When NOT to use this2526- Read-only query changes, seed scripts, or data backfills that touch no schema.27- A repo with a healthy, linear migration history and CI that enforces it — the28 full ritual is overhead there. Use it where a mistake is expensive.29- Applying migrations **to production** — this skill prepares and verifies; it30 does not run `db:migrate` against a shared/prod database. That is a human,31 deliberate, out-of-band step.3233## The four failures this prevents34351. **Reused prefix** — a new `0034_…` when `0034` already exists. Derive the36 next number from what's on disk, never from memory.372. **Silent destructive SQL** — `db:generate` emitting `DROP COLUMN`/`DROP TABLE`38 because a rename or removed field read as a deletion. Every generated file is39 grepped for destructive ops and shown to the user before it's kept.403. **Schema/migration drift** — a hand-written `ALTER … DROP` with no matching41 edit to the schema file (the incident that broke prod for weeks). Schema and42 migration must land in the **same commit**; the skill refuses to finish43 otherwise.444. **`db:push` to a shared DB** — skips the migration files and drifts schema vs45 DB. Never run against anything but a local dev database.4647## Workflow48491. **Load the repo's rules.** Read the migrations directory's own README (search50 `**/migrations/README.md`) and the root `CLAUDE.md`/`AGENTS.md` migration51 section. They are the source of truth for prefix scheme, the check command,52 and repo-specific hazards. Where they differ from this file, they win.53542. **Find the next free prefix — from disk, not memory:**55 ```bash56 ls <migrations-dir>/*.sql | sed 's#.*/##' | sort | tail -557 ```58 Next prefix is `lastNumber + 1`, zero-padded to the repo's width. If the59 directory has historical duplicate prefixes, tolerate them but do not add a60 new collision — scan before naming.61623. **Edit the schema file(s)** for the actual change, matching the file's63 existing conventions (read them; do not assume).64654. **Generate:**66 ```bash67 <pkg-manager> db:generate # e.g. pnpm db:generate -> drizzle-kit generate68 ```69 Then find the file it wrote (newest `.sql`).70715. **Grep the generated SQL for destructive operations and SHOW the user:**72 ```bash73 grep -niE 'drop (table|column|constraint|index|type)|truncate|alter .*drop|delete from' <new.sql>74 ```75 Any hit is a stop-and-confirm. A `DROP` is only correct when the user76 genuinely intends to remove something; a `DROP` that surprises you is the77 `db:generate` hazard firing — do not keep the file, fix the schema and78 regenerate. See `references/hazards.md`.79806. **Harden hand-written SQL.** If you (not the generator) wrote any SQL, make81 every statement idempotent — `IF NOT EXISTS` / `IF EXISTS` — and confirm the82 schema file carries the matching change in this same working tree.83847. **Run the repo's migration check** (use what step 1 found, commonly):85 ```bash86 <pkg-manager> db:check-migrations # or drizzle-kit check87 ```88 Historical-dupe / journal-drift **warnings** are expected on a damaged89 directory and are fine; a **failure** is not.90918. **Stage schema + migration together, in one commit.** List the staged files92 back to the user and confirm both are present before committing. Never commit93 a migration without its schema change, or vice versa.9495## The prod boundary (state it every time)9697- **Never** `db:push` / `db:migrate` against staging or prod from this skill.98- A migration must be **applied to prod before the code that depends on it99 deploys**, or the new column/table 500s the live app. Say this in the report;100 the apply itself is the user's deliberate step.101- If asked to check prod schema drift before shipping, do it **read-only** and102 say plainly you are only reading.103104## Report format105106- The change, the new migration filename, and the exact prefix reasoning.107- The destructive-op grep result — "none" is a result worth stating.108- Confirmation that schema + migration are staged together.109- The prod-apply reminder, and whether the check passed (warnings vs failures).110111## References112113- `references/hazards.md` — the real incident shapes (reused prefix, silent114 DROP, schema drift, db:push) and how each is caught here.