cd /news/developer-tools/round-trip-tests-catch-bad-down-migr… Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-104381] src=sourcefeed.dev β†— pub= topic=developer-tools verified=true sentiment=Β· neutral

Round-Trip Tests Catch Bad Down Migrations, Not Data Loss

AI agents are generating database down migrations that often fail to restore the original schema, and a round-trip test in CI can catch schema drift but not data loss, according to a developer article by Rachel Goldstein. The test, which applies and reverts a migration against a disposable database and diffs the schema, costs little to implement but cannot prove reversibility for destructive operations like dropping columns, which inherently lose data. The article notes that tools like Prisma and Flyway have already moved away from relying on down migrations for data restoration.

read5 min views4 publishedAug 20, 2026
Round-Trip Tests Catch Bad Down Migrations, Not Data Loss
Image: Sourcefeed (auto-discovered)

Dev ToolsArticle

AI agents are reviving the down migration just as the industry finished giving up on it.

Rachel Goldstein

Coding agents write a lot of database migrations now, and the pattern of failure is consistent: the up

script gets real scrutiny because it has to run for the feature to work, while the down

script gets merged on vibes. A model writing ALTER TABLE

has your target schema in context; it rarely has the baseline schema it's supposed to restore. So the down script it produces is a plausible-looking guess β€” syntactically valid, confidently wrong about a constraint name, a column default, an index that existed before. You find out during an incident, which is the one time you wanted that script to be boring.

There's a cheap defense making the rounds: round-trip the migration in CI against a disposable database and diff the schema. It's a good check and you should run it. But it's worth being precise about what it proves β€” because the thing it can't prove is exactly the thing the industry spent the last decade concluding down migrations can't deliver.

The round-trip test costs almost nothing #

The mechanics fit in a CI job you can write in an afternoon. Spin up a throwaway Postgres (a service container is fine), load the current schema, snapshot it, apply the migration forward, apply it backward, snapshot again, and demand a byte-identical result:

pg_dump --schema-only -f baseline.sql "$DB"
psql "$DB" -v ON_ERROR_STOP=1 -f migrations/0042_split_billing/up.sql
psql "$DB" -v ON_ERROR_STOP=1 -f migrations/0042_split_billing/down.sql
pg_dump --schema-only -f after.sql "$DB"
diff baseline.sql after.sql   # any output fails the build

Text-diffing dumps gets noisy if you're comparing across server versions; a semantic comparison with migra or atlas schema diff

is sturdier. Rails users have had a one-liner version of this forever β€” db:migrate:redo

applies and reverts the latest migration, and running it in CI catches most IrreversibleMigration

surprises β€” though it doesn't assert the schema actually matches the starting point, which is the part that catches subtle drift.

The reason this check pays off specifically for generated migrations: the failure output is a targeted repair prompt. "Down migration left index idx_orders_user_id

missing" is exactly the context the model lacked the first time. You fix the down script, not the whole migration.

Schema symmetry is not reversibility #

Here's the trap. Consider the most common destructive migration there is:

-- up.sql
ALTER TABLE users DROP COLUMN legacy_plan;
-- down.sql
ALTER TABLE users ADD COLUMN legacy_plan text;

This round-trips perfectly. The schema diff is empty, CI is green, and the down migration has destroyed every value in that column. Schema symmetry proves the down script restores the shape of your database. It says nothing about the substance.

You can extend the harness to catch this β€” seed representative rows before the round trip and checksum them after. A SELECT md5(string_agg(...))

per table before and after will flag any migration pair that loses data in transit, including the drop-and-recreate case above. That's a meaningfully stronger contract, and almost nobody tests it, because it forces you to confront that many migrations are inherently irreversible: dropped columns, narrowed types, merged tables, backfills that collapse information. No down script, however carefully generated and tested, can un-lose data.

The industry already ruled on this #

That's why the tooling landscape looks the way it does. Prisma doesn't generate down migrations at all β€” if you want one, you construct it yourself with migrate diff

. Flyway treats undo migrations as a paid Teams-tier feature, and Redgate's own guidance is candid that undo scripts can't recover destroyed data. Large shops mostly converged on roll-forward-only: a bad migration is fixed by shipping another migration, and actual disaster recovery is a snapshot restore, not a down

method. The down script quietly became a local-development convenience β€” handy for iterating on a branch, not a production recovery plan.

The tools that genuinely solve rollback solve it by refusing to play the game. pgroll keeps both the old and new schema live simultaneously through views during an expand/contract window, so "rollback" means discarding a schema version that's still there β€” no reverse script to get wrong. Atlas attacks it from the review side: atlas migrate lint

flags destructive and backward-incompatible statements at PR time, which is arguably the more honest check β€” instead of asking "can this be undone?", it asks "does this destroy something?"

So there's a real irony in the current moment. AI codegen is reviving an artifact β€” the symmetric down migration β€” at exactly the point the ecosystem had finished admitting it was mostly theater. Agents produce down scripts by default because their training data is full of them, and teams that had comfortably gone roll-forward-only are now merging reverse migrations nobody intended to run.

What to actually do #

Run the round-trip test β€” it's nearly free, it catches the class of error generated code is most prone to, and its failures are self-explaining. Add data checksums if you want the honest version. But classify what you've built correctly: it's a lint for generated SQL, not a rollback guarantee.

For production, the hierarchy hasn't changed. Destructive changes go through expand/contract (or pgroll, which automates it), so old and new code can coexist and "rollback" is a deploy decision rather than a schema operation. Recovery from a genuinely bad migration is point-in-time restore. And a down script that's never been executed against realistic data should be treated as what it is: a comment with delusions of grandeur. Test it in CI precisely so you know it works in the one place you'll ever run it β€” your laptop.

Sources & further reading #

Prove a Generated Migration Can Undo Itself Before It Touches Your Dataβ€” dev.to - Generating down migrationsβ€” prisma.io - Undo migrationsβ€” documentation.red-gate.com - Introducing pgroll: zero-downtime, reversible, schema migrations for Postgresβ€” xata.io - Verifying Migration Safetyβ€” atlasgo.io

Rachel GoldsteinΒ· Dev Tools Editor

Rachel has been embedded in the developer tooling ecosystem for nearly eight years, covering everything from IDE wars and package-manager drama to the quiet rise of AI-assisted coding. She has a soft spot for open-source maintainers and an unhealthy number of terminal emulators installed on a single laptop.

Discussion 0 #

No comments yet

Be the first to weigh in.

── more in #developer-tools 4 stories Β· sorted by recency
── more on @rachel goldstein 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/round-trip-tests-cat…] indexed:0 read:5min 2026-08-20 Β· β€”