Last reviewed: 2026-07-30

A database migration can be syntactically valid and still delete data, fail on existing rows, block application traffic, or leave code and schema out of sync. A coding agent can accelerate migration work, but its output needs a database-specific review rather than ordinary source-code approval.

Direct answer

A coding agent database migration review should treat every generated migration as a proposal, not a verdict. Review the intended model change, the migration program, the exact SQL the target database will execute, its effect on representative data, and a tested recovery path. Approval should require evidence for all five.

That standard is consistent with the Alembic autogenerate documentation , which calls autogenerated files candidate migrations and says they must be manually reviewed and corrected. Alembic also identifies an especially dangerous case: table and column renames can appear as an add-and-drop pair rather than a rename.

A reviewer should be able to answer four questions without relying on the agent’s explanation:

  1. Does the migration implement the requested change and nothing broader?
  2. Does the rendered SQL preserve the data that the application still needs?
  3. Can the application operate safely during every deployment stage?
  4. If application or migration checks fail, is the recovery procedure explicit and tested?

Happy-path operator workflow

  1. Write the contract before generating anything. Record the current migration head, target database engine, intended schema change, data that must survive, acceptable lock or maintenance behavior, deployment order, and recovery expectation. A request to rename a field should say that existing values must remain intact; otherwise a generator may legally satisfy the schema shape by dropping one column and creating another.

  2. Generate from a clean baseline. Keep the model or schema edit and its migration together. Confirm that the agent did not modify unrelated migrations, rewrite migration history, or silently change configuration. Save the generator command and resulting file list.

  3. Inspect the migration and render its SQL. Django documents sqlmigrate as the command that displays a migration’s SQL, while makemigrations creates migration files and migrate applies or unapplies them. See the Django migrations documentation . For tools that support drafts, use that mode: the Prisma migration customization guide describes creating a migration with --create-only, editing the generated SQL, and applying it only afterward.

  4. Classify every operation. Mark each statement as additive, data-moving, constraint-building, index-building, modifying, or destructive. Explain every DROP, type conversion, new non-null constraint, uniqueness rule, foreign key, and data update. A destructive statement needs a direct connection to the approved contract and evidence that required data has already moved or is intentionally disposable.

  5. Apply it to a disposable database using the same engine family as production. Seed representative, sanitized fixtures, including nulls, duplicates, long values, referenced rows, and enough records to expose slow scans. Capture pre-migration row counts and targeted invariants. Do not substitute an empty database for this test.

  6. Run schema, data, and application assertions. Verify the expected columns and constraints, preserved values, relationship integrity, application reads and writes, and any backfill totals. Confirm that a second application attempt behaves as the migration framework expects rather than partially repeating work.

  7. Exercise recovery. Where the engine and operations support transactional DDL, test failure before commit and confirm rollback behavior. The PostgreSQL BEGIN documentation explains that statements after BEGIN run in one transaction until COMMIT or ROLLBACK. Do not generalize that behavior to every engine. Django notes that PostgreSQL and SQLite support DDL transactions for migrations by default, while MySQL and Oracle do not. On a non-transactional path, test snapshot restoration or a forward repair procedure instead.

  8. Rebuild and apply again from the original baseline. A successful re-run proves that the checked-in migration, not unrecorded manual state, produced the result. Attach the rendered SQL, assertions, recovery result, and final diff for a reviewer who did not author the change.

Error-path operator workflow

Suppose the request is to rename biograpy to biography, but the rendered SQL adds biography and drops biograpy. Stop before application. Both Alembic’s documented rename limitation and Prisma’s rename example show why this pattern can destroy existing values.

First, preserve the generated diff as evidence and classify the mismatch as a data-loss risk. Then choose an engine-supported rename or an expand-and-contract sequence: add the new field, write both fields, backfill and verify values, switch reads, stop writes to the old field, and remove it in a later migration. Prisma documents this staged pattern for reducing downtime around fields already used by application code.

If a test application fails, do not blindly retry. Record the failed operation class and sanitized diagnostics. Roll back the disposable transaction when supported; otherwise discard or restore the test database. Correct the schema input or migration, recreate the original baseline, render the SQL again, and repeat every check. Escalate if the failure involves an unexplained destructive statement, unbounded lock exposure, uncertain data conversion, or an untested recovery path.

Who this is for

This workflow is for engineers who review migrations produced or modified by coding agents, including application developers, database specialists, release engineers, and pull request reviewers. It applies whether the repository uses an ORM migration generator, handwritten SQL, or a mixture of both.

It is particularly useful when a change includes existing production data, a rename, a type conversion, a new uniqueness or foreign-key rule, a large backfill, or an application deployment that cannot switch atomically with the schema. Small-looking schema diffs can have large operational effects, so repository size is not a reliable measure of migration risk.

The reviewer does not need to distrust the agent. The goal is to separate authorship from proof. An agent may generate a good candidate quickly; the operator still needs evidence that the target database and current data satisfy the candidate’s assumptions.

Key takeaways

  • Autogenerated migrations are reviewable candidates, not automatically safe changes.
  • Compare intent, migration code, and rendered SQL; none is an adequate substitute for the others.
  • Test with representative data on the same database engine family, not only an empty local database.
  • Treat add-and-drop output for a requested rename as a stop condition until data preservation is proven.
  • Verify deployment ordering and mixed-version application behavior, not just the final schema.
  • Test rollback, restoration, or forward repair according to the target engine’s actual transaction behavior.
  • Keep sanitized evidence so an independent reviewer can reproduce the decision.

Sources checked

Contract details to verify

Use a compact review contract to keep the agent’s narrative from replacing observable evidence.

Review itemReviewer questionPass condition
IntentWhat schema and data behavior was requested?The requested scope is explicit and bounded.
Migration lineageWhat migration must run immediately before this one?Dependencies point to the expected current head.
Rendered operationsWhat will the target engine execute?Every operation is explained and expected.
Data invariantsWhat values and relationships must survive?Assertions pass before and after migration.
Compatibility windowCan old and new application versions coexist?Deployment order and mixed-version behavior are tested or explicitly constrained.
RecoveryWhat happens after a partial or failed application?Rollback, restoration, or forward repair is rehearsed.
EvidenceCan another reviewer reproduce the result?Commands, sanitized logs, assertions, and diffs are attached.

Source and migration agreement

Compare the human-authored schema change with the generated migration. A new model field should have a corresponding operation; an unrelated table change should not. Confirm that migration dependencies are correct and that model and migration files land together. Django describes migration files as a version-control system for schema state and recommends committing a model change with its accompanying migration.

SQL and data agreement

Rendered SQL is the execution contract. Look for implicit casts, default values, full-table updates, add-and-drop rename patterns, constraint validation, and ordering dependencies. Before applying, write assertions for row counts, preserved values, nullability, uniqueness, and foreign-key relationships. After applying, run the same assertions plus application-level reads and writes.

A generator warning is evidence, not noise. Prisma’s documented relation-change example warns about possible data loss and duplicate values before a unique constraint is added. A reviewer should convert such a warning into a precondition query or fixture that proves whether the migration can proceed.

Deployment agreement

A migration can pass in isolation and still fail during deployment. Determine whether the old application can use the new schema and whether the new application can run before every migration step completes. For a live field change, prefer additive compatibility and a staged contract when one-step replacement would create downtime or destroy data.

Transaction and recovery agreement

Name the target engine and do not infer transaction behavior from a different local database. If all operations are transactional, inject a controlled test failure and verify that intermediate state is not visible after rollback. If they are not transactional, identify the last durable step, restoration source, retry boundary, and forward repair. A downgrade function alone is not proof: run it against the disposable fixture and validate the data afterward.

Sanitized evidence fields

Record operational facts without copying row contents, connection strings, or environment-provided authentication material. A compact record can look like this:

{
  "migration_id": "0007_rename_profile_field",
  "database_engine": "postgresql",
  "test_environment": "synthetic-fixture-db",
  "schema_before": "baseline_42",
  "schema_after": "candidate_43",
  "operation_summary": ["rename_column", "backfill_rows"],
  "rows_examined": 1200,
  "rows_changed": 1200,
  "lock_wait_ms": 18,
  "apply_result": "passed",
  "recovery_result": "passed",
  "diagnostic_excerpt": "[REDACTED]"
}

Useful fields include the migration identifier, engine, baseline, command class, start and finish times, operation categories, affected-row counts, lock-wait observation, assertion totals, apply result, and recovery result. Keep full SQL as a reviewed artifact when it contains no sensitive data; keep data values out of the log.

Failure modes

  • A rename becomes add-and-drop. The final schema looks right, but old values disappear. Stop and replace the candidate with a true rename or staged data move.
  • A non-null or unique constraint assumes clean data. The migration succeeds on an empty database and fails on real nulls or duplicates. Add representative fixtures and explicit precondition checks.
  • A backfill and constraint are ordered incorrectly. A constraint is enforced before existing rows are transformed. Separate the operations and verify the intermediate state.
  • The reviewer assumes transactional DDL everywhere. A failed migration leaves durable partial changes on an engine without the expected transaction support. Test the actual engine and document restoration or forward repair.
  • The migration and application deploy in the wrong order. New code reads a field that does not exist yet, or old code writes only the field scheduled for removal. Define the compatibility window and stage the rollout.
  • A large-table operation is treated like a small fixture. Tests prove correctness but say nothing about lock or scan exposure. Use representative scale, record timing observations, and escalate when the acceptable window is unknown.
  • The agent rewrites old migration history. Existing environments and fresh installations no longer share one lineage. Require a new migration unless the repository’s documented policy explicitly permits an unreleased migration to change.
  • A passing apply test replaces recovery evidence. The happy path succeeds, but nobody knows what to do after a partial failure. Rehearse rollback, restoration, or forward repair before approval.
  • Logs contain data instead of evidence. Raw rows make review artifacts risky and noisy. Store counts, operation classes, results, and redacted diagnostics instead.

FAQ

Should a coding agent ever edit a generated migration?

Yes, when the framework supports reviewable draft migrations and the generated candidate does not preserve the required behavior. Alembic explicitly expects manual review and correction, and Prisma documents editing draft SQL before application. The edit must remain tied to the schema intent, rendered SQL, and repeatable test evidence; it should not be an unexplained patch to make one test pass.

Is a successful migration on a fresh database enough?

No. A fresh database cannot reveal loss of existing values, duplicate-key failures, nullability conflicts, backfill mistakes, or realistic scan behavior. Test both fresh installation when relevant and upgrade from a representative prior schema containing edge-case data.

Must every migration have a working downgrade?

Every migration needs a credible recovery plan, but that plan is not always a literal downgrade. Some destructive changes cannot reconstruct discarded data, and some engines may leave partial DDL outside a transaction. The safe plan may be rollback, snapshot restoration, or a tested forward repair. State which one applies and demonstrate it on a disposable database.

Can CI approve the migration automatically?

CI can render SQL, build a representative database, apply the migration, run invariants, exercise a supported recovery path, and preserve evidence. It cannot decide whether unexplained data loss or downtime matches business intent. Keep human approval for destructive operations, uncertain conversions, and deployment compatibility decisions.

What should trigger immediate escalation?

Escalate an unexplained DROP, a requested rename rendered as add-and-drop, a failed data invariant, uncertain target-engine behavior, an unknown lock window, a missing migration dependency, production-only assumptions, or any recovery procedure that has not been tested. These are contract failures, not prompts for an unbounded agent retry loop.

Reader next step

Choose one pending agent-written migration and write its review contract before opening the generated file: intent, protected data, rendered operations, compatibility window, and recovery method. Recreate the prior schema in a disposable database, run the happy path, deliberately exercise the error path, and attach the sanitized evidence to the pull request.

Use change scope notes to bound the request, then produce a reviewable diff that separates schema inputs, migration code, and generated SQL. Before approval, define stop, retry, and escalation conditions so a failed test produces a controlled decision rather than a blind rerun.