NO FEED · AI-assisted development
Review an AI-generated database migration before it touches real data
Review AI-generated PostgreSQL migrations with compatibility checks, lock analysis, resumable backfills and recovery checkpoints before touching real data.
On 20 September, Reddit user balramverma reported losing a local development database after an agent ran Laravel migrate:fresh. The pasted explanation said it reached MariaDB instead of the intended SQLite test database. Configuration and outcome were not independently verified. [1]
Review an agent-generated migration through its target database, application compatibility, locking, data transformation and recovery path. Valid SQL alone leaves all five questions open. Ask for evidence from a disposable rehearsal before scheduling the change.
Consider a hypothetical billing service changing invoices.customer_ref, a text reference, to invoices.customer_id, a foreign key. The agent proposes adding the column, filling it, making it mandatory and deleting the old reference in one migration. Its fixture has three invoices and every reference matches a customer. The test passes. That leaves unanswered what old workers will write during deployment, what happens to unmatched references and how the backfill interacts with current updates.
This is a proposed review workflow, not a migration NO FEED has executed. Database details below refer to the PostgreSQL 18 documentation read on 19 October 2026.
Confirm the rehearsal database first
Laravel’s documentation confirms that migrate:fresh drops tables on the selected connection before migrating. [2] The lesson for our PostgreSQL rehearsal is narrower than the incident’s headline: establish the connection actually opened, and restrict rehearsal credentials to disposable data. A command described as a test does not establish its target.
Define which application versions must keep working
Start with a compatibility table. Include the web application, background workers and administrative scripts that read or write the changed field. The last old process matters as much as the first new deployment.
Prisma’s expand-and-contract guide describes adding the new structure alongside the old, moving clients and data in stages, then removing the old structure. Its sequence also highlights a recovery limit: new information that cannot be represented in the old schema makes a simple return to the old interface incomplete. [3]
For the invoice example, propose these checkpoints:
| Phase | Reader and writer behavior to demonstrate | Reason to stop |
|---|---|---|
| Expand | Old clients still work; new nullable field exists | Existing inserts fail or new code requires an absent value |
| Prepare writers | The chosen synchronization path maintains both representations | A worker or import writes only the obsolete representation |
| Backfill | Existing rows gain correct IDs; interruption and restart preserve newer writes | Unmatched references, inconsistent mappings or overwritten updates |
| Switch reads | New readers work; an explicitly supported fallback still works | Missing IDs or materially different results |
| Contract | No supported client needs the old field | A deploy rollback, delayed job or export still references it |
This is NO FEED’s review table for the hypothetical change, not a universal deployment recipe. Dual writes need their own consistency design. Two independent application writes can fail separately; the review should identify the transaction, trigger or other mechanism that maintains the chosen invariant. “Write both columns” does not answer that question.
Inspect the generated statements and their locks
Read the SQL emitted by the migration framework. An innocuous model rename can produce a destructive operation if the generator interprets it differently. Ask the agent to annotate each statement with its object, expected lock, data scan or rewrite, and transaction boundary.
PostgreSQL documents ACCESS EXCLUSIVE as the default lock for ALTER TABLE unless a particular form says otherwise. Combining subcommands takes the strongest required lock. For supported constraints, NOT VALID can separate initial enforcement on new changes from checking historical rows; later validation still scans the table and takes a lock. It does not make the operation cost-free. [4]
For a needed index, CREATE INDEX CONCURRENTLY has a different operational contract from an ordinary index build. It cannot run inside a transaction block; a failed build can leave an invalid index, including continuing overhead. Review how the framework handles that statement and how the operator will inspect a failed attempt before retrying. [5]
Set an explicit waiting policy for the migration session. PostgreSQL’s lock_timeout limits time spent waiting for each lock acquisition; statement_timeout limits statement duration. Neither tells you an appropriate value for this service. Choose limits from the service’s tolerance and the rehearsal, and decide who investigates a timeout. An agent retrying indefinitely defeats the intended stop. [6]
Make the backfill safe to interrupt
The invoice transformation needs a business rule before SQL: does every historic reference identify exactly one customer? What happens when it identifies none, or when normalization makes two references equal? The agent should surface those rows for a decision, not invent a customer to satisfy a constraint.
PostgreSQL warns that large updates can increase table bloat, replica lag and lock contention, and describes smaller batches as one mitigation. Its UPDATE documentation also explains why a join that yields multiple source matches can give an unexpected update result. [7]
Our proposed backfill review asks for four concrete properties. First, a bounded batch with a stable selection rule. Second, progress that survives interruption. Third, a way to distinguish unfinished rows from completed ones. Fourth, protection against replacing a value written by a newer application request. The implementation depends on the data model; a generic batch size does not establish these properties.
Rehearse interruption after a batch, a duplicate source mapping, a deliberately unmatched reference and a concurrent application update. Compare the affected rows with the intended mapping. A row count alone can pass while the wrong customers receive the invoices. Keep the data invariant beside the test so the reviewer can see what “correct” means.
Separate application rollback from data recovery
Reverting the application commit does not reverse an already committed transformation. In the hypothetical example, retaining the original reference may allow old readers to resume, provided it has stayed current. Deleting that field removes this option. A newly added relationship might also express information the old representation cannot store. [3]
Write a recovery decision for each checkpoint: stop the backfill, return readers to the old representation, repair forward, or restore data using the established recovery process. Identify the last point at which each option remains valid. A generated down migration that recreates an empty column is not evidence that its previous contents can be recovered.
For a disposable rehearsal, our proposed evidence packet contains the before/after schema, compatibility results, rejected-row report, backfill restart result and the tested recovery path. Record representative data shapes and load separately: success on a small fixture is useful functional evidence, but does not establish production timing or contention.
Use an isolated database for this work; our worktree isolation analysis[8] explains why a separate checkout does not provide one. Then ask the agent for the earliest phase whose recovery plan depends on data you are about to remove. Resolve that phase before approving the migration.
Sources & context
- u/balramverma — Claude code wiped entire databaseLocal-development report, 20 September 2026; original and replies checked on 19 October. Not independently reproduced.
- Laravel 12.x — Drop All Tables and MigrateCommand semantics, not confirmation of the reported configuration; read 19 October 2026.
- Prisma — Using the expand and contract patternPattern guidance; read 19 October 2026.
- PostgreSQL 18 — ALTER TABLEStatement-specific locks and constraint validation; read 19 October 2026.
- PostgreSQL 18 — CREATE INDEXConcurrent-build restrictions and failed builds; read 19 October 2026.
- PostgreSQL 18 — Client connection defaultsLock and statement timeouts; read 19 October 2026.
- PostgreSQL 18 — UPDATEBatch updates and join ambiguity; read 19 October 2026.
- our worktree isolation analysisRead 19 October 2026.