TL;DR
An AI coding agent can draft a migration, write the queries that check it and run it against a copy. It cannot know which data exists nowhere else, and it should not hold credentials that reach production or make the go decision. Restore a real backup into a database the agent's role can reach, run the migration there, compare counts, totals and per-row fingerprints with the original, and move on only when they match and a person approves.
What the seed script could not bring back
Hand-picked product images and their order, an admin account, store settings: those were the losses listed in a Claude Code bug report from April 2026, about a day of manual work. The agent had been asked to apply one new migration. It ran supabase db reset, which, as Supabase's CLI reference describes, recreates the local database, applies every migration and the seed file, and discards anything else. The incremental command, supabase migration up, applies only pending files. The seed script brought the catalogue back. The curation had lived only in that database.
Two questions from other places show the same gap from the delegating side. In Supabase's GitHub discussions, a developer using Codex asked whether it was risky that the Supabase MCP server let the agent apply a migration directly, without a migration file, leaving the local project out of step. On the OpenAI developer forum, someone running a Postgres application with more than 120 tables asked how others keep a coding agent oriented in a schema whose access rules live in the database catalog rather than the application code.
The split follows from those cases. An agent is good at the typing: the migration, the checks, the runbook. It cannot see which rows exist nowhere else, which systems still read a column, or whether the database in front of it is one you can afford to lose. Those answers have to exist before it starts, and the credentials it holds should make its worst mistake a cheap one.
Write down what has to survive
Before the agent drafts anything, list what the migration must preserve and how each item will be checked:
- Data that exists only here: rows entered or curated by hand, history, anything a seed or import cannot regenerate. Name the tables and the counts or totals that prove they are intact.
- Dependants the migration file does not show: foreign keys and their delete rules, triggers, views, scheduled jobs, reports, and other services or older app versions still reading a column.
- Behaviour: uniqueness, defaults, access rules, and what the application does with the rows the change touches.
Some of the list is a decision rather than a fact. Merging duplicate accounts means choosing which record survives and what happens to the other's history. The agent can set out the options; a person who owns the data should pick one. With the inventory written, the division of work is clear:
Comparison
| Hand to the agent | Keep with a person |
|---|---|
| Drafting the migration and its reverse | The inventory of data and dependants that must survive |
| Writing reconciliation queries from that inventory | Business rules, such as which duplicate account wins |
| Running the migration on a restored copy and reporting the raw output | Credentials for production and for the backups |
| A runbook with timings taken from the rehearsal | The go decision and the production run itself |
Give the agent a database it is allowed to lose
Isolation that depends on the agent choosing the right command is not isolation. The boundary that holds is the credential: a database role that can connect only to a rehearsal database restored from a recent backup, and no production connection string in any environment file, MCP configuration or shell history the agent can read. In the demonstration below, a role set up this way was refused a connection to the original database and refused permission to drop it.
Tool settings are a second layer, not a substitute. Claude Code's permission rules can deny a command such as Bash(supabase db reset *), but its documentation says a Bash deny rule "isn't a security boundary around the program": the same program called by another path or through sh -c is not matched. Prisma's CLI refuses prisma migrate reset --force from a detected agent until an environment variable records consent, which the agent is told to set only after asking you. Both are useful speed bumps. A role that cannot reach production is the part that does not rely on anyone's judgement in the moment.
If several agents work at once, each needs its own database as well as its own checkout. The article on running coding agents in parallel without them overwriting each other's changes covers what stays shared between worktrees.

A rehearsal that passed every check but one
To see what a rehearsal catches, a small one was run for this article on PostgreSQL 18.6 in a throwaway container. A synthetic database stood in for production: 2,040 customers, 40 of them second accounts whose email differs only in letter case, and 10,000 orders whose foreign key uses ON DELETE CASCADE. The goal was a unique index on lower-cased email. Two migrations were written by hand, not by an agent. Migration A keeps the oldest account for each email and deletes the rest. Migration B first moves each duplicate's orders to the surviving account and records every merged row in a log table, then deletes.
A pg_dump backup of the original was restored into a fresh rehearsal database for each run, and each migration ran as the restricted role. Before and after, the same queries recorded order count, order total, distinct emails and a fingerprint of every order with its owner's email:
SELECT md5(string_agg(o.id || ':' || o.total_cents || ':' || lower(c.email), ',' ORDER BY o.id))
FROM orders AS o JOIN customers AS c ON c.id = o.customer_id;
Migration A did exactly what it was asked, and every check an agent would usually report passed. The cascade removed 198 orders along with the duplicate accounts, and only the comparison with numbers taken before the run showed it. Before either migration, the restored copy's fingerprint matched the original's. That is the restore check: a backup counts once it has been restored and compared, not when the dump command exits cleanly. The original database was unchanged at the end.

Decide what lets it reach production
Agree the evidence before the rehearsal runs, so the result is judged against something fixed:
- Restore: a recent backup restored and fingerprinted, with the restore timed at production size.
- Reconciliation: every item on the inventory matches, or differs exactly as planned. In migration B, 40 fewer customers and 40 log rows were the expected difference.
- Run time and locks: measured on production-sized data, with a window that fits.
- A way back: the reverse migration run on the rehearsal copy, or the restore accepted as recovery, knowing that writes made after the backup would need separate handling.
- Approvals: the data owner signs the inventory and business rules; someone other than the agent reviews the raw rehearsal output and makes the go decision.
Then a person runs production with the rehearsed commands, and the same queries run before and after. The article on whether an AI coding agent should be allowed to merge its own pull request explains why those grants belong to different people.
Paqad is not a migration tool, a backup or a permission boundary. When a request is a migration, Paqad's routing always puts it in its full lane, which on Claude Code and Codex CLI means a recorded plan and frozen specification before the first code edit; while a decision waits for your answer, further code edits are blocked. Its hooks watch file edits only: a db reset or DROP TABLE run through the shell is not checked, and in paqad-ai 1.91.1 no gate requires a database review before a migration. Paqad's documentation sets out what its guardrails stop and what they leave to your tool's permissions.
Frequently Asked Questions
How much of a database migration can I safely delegate to an AI coding agent?
Scale it to how reversible each step is. Adding a table, a nullable column or an index can go further with less ceremony. Steps that drop, rewrite or merge data are better split, so the new structure ships and is used first and the destructive step runs later, as its own rehearsed change, once nothing depends on the old shape.
What data, dependencies and existing behaviours must survive the migration?
Build the dependency list from the database catalog rather than from application models: foreign keys with their delete rules, triggers and views that reference the tables. In the demonstration the damage came from a cascade defined in the schema, which the migration file never mentioned. Then add readers outside the database, such as reports and other services.
What permissions and isolated environment keep a mistaken command away from important data?
A role that can reach only a restored copy, and backups the agent's credentials cannot delete or overwrite. Keep production secrets out of every file and configuration the agent loads. Deny rules in the agent's tool reduce accidents, but test the boundary by trying the forbidden connection yourself with the agent's credentials.
How should rehearsal, reconciliation and recovery be designed before any production change?
Write the reconciliation queries before the migration and run them on the untouched copy first, so both the baseline and the queries are proven. Record the expected differences in advance, then rehearse the recovery path as well as the forward one, with timings, so the rollback is a measured option rather than a hope.
What measured results and human approvals are required before the migration is allowed to proceed?
Matching or explained reconciliation, a restore checked against the original, measured run time and locking, and a stop condition written in advance, such as a count difference that aborts the run. The approver should read the rehearsal output itself, not the agent's summary of it, and the approval should name the backup it relied on.
