Ilustración abstracta de una cadena de bloques numerados en la que uno aparece retocado con un color distinto mientras los demás permanecen alineados

Migración ya aplicada: tu agente la edita y nadie lo ve


Respuesta corta: cuando un agente de código modifica un archivo de migración que ya se aplicó en alguna base de datos, ese cambio no se ejecuta nunca allí. Las herramientas de migración registran qué migraciones han corrido por su identificador, no por su contenido. La regla segura es simple: lo que ya se aplicó fuera de tu máquina no se edita; se corrige con una migración nueva.

El síntoma: funciona en tu base limpia y falla en la de los demás

La pista típica es que todo va bien en tu entorno y falla en staging, en CI o en la máquina de un compañero con un error de columna inexistente. Supongamos que le pides al agente "añade el campo telefono a Cliente". En lugar de generar una migración nueva, abre la última migración existente y le añade la columna. El diff es más corto y parece más limpio.

Si tú recreas la base desde cero, la migración editada corre entera y la columna aparece. Pero en cualquier base donde esa migración ya estaba aplicada, la herramienta ve que el identificador ya figura como ejecutado y se la salta. El código espera la columna; la base no la tiene. Los tests en verde de tu máquina no te avisan, porque probablemente parten de una base nueva.

Otras variantes del mismo problema que conviene reconocer en un diff:

  • El agente renombra una migración para "ordenarla". Para la herramienta es una migración nueva sin aplicar y otra que ha desaparecido.
  • El agente borra una migración que "ya no hace falta" porque otra posterior deshace su efecto.
  • El agente corrige un error de sintaxis SQL dentro de una migración que en producción ya corrió (con otro contenido).

La causa: el historial se registra por identificador, no por contenido

Las tres herramientas más habituales guardan en la propia base de datos una lista de migraciones aplicadas, y ninguna vuelve a ejecutar una que ya figure ahí. Lo que cambia entre ellas es si detectan que el archivo se ha tocado después.

En Django y Alembic el fallo es silencioso. En Prisma es ruidoso, pero el ruido trae su propio riesgo: la salida sugiere un reset, y un agente con permiso para ejecutar comandos puede tomar esa sugerencia como el siguiente paso.

Por qué un agente cae en esto con facilidad

Editar la migración existente es la solución que menos líneas toca, y eso la hace tentadora para cualquiera que no sepa en qué bases ya se aplicó, sea persona o agente. El agente solo ve el repositorio. No sabe si esa migración ya corrió en staging, en producción o en el portátil de otra persona, porque esa información vive en las bases de datos, no en los archivos.

Además, si el agente valida su trabajo recreando la base local, la comprobación confirma justo el escenario que funciona. Por eso la detección tiene que basarse en algo que el agente no pueda "arreglar" desde su contexto: el historial de git respecto a la rama principal.

Cómo confirmarlo en dos minutos

La prueba más fiable es preguntarle a git qué archivos de migración ha modificado, borrado o renombrado tu rama respecto a la principal. Las migraciones nuevas aparecen como añadidas (A); cualquier M, D o R sobre una migración merece revisión.

Este comando lista solo las migraciones tocadas que ya existían en main. Usa la notación de tres puntos, que según la documentación de git compara desde el ancestro común, y --diff-filter para quedarse con modificadas, borradas y renombradas:

git fetch origin main
git diff --name-status --diff-filter=DMR origin/main...HEAD -- \
  'migrations/*' '*/migrations/*' 'alembic/versions/*'

Si la salida está vacía, tu rama solo añade migraciones. Si aparece algo, comprueba después el estado en la base que te preocupa con el comando de tu herramienta:

HerramientaDónde registra lo aplicado¿Detecta una migración editada?Comando para comprobar
DjangoTabla de migraciones en la baseNopython manage.py showmigrations y python manage.py makemigrations --check
Alembicalembic_versionNoalembic current y alembic check
Prisma_prisma_migrations con checksumSí, avisa de que se modificónpx prisma migrate status

Dos detalles útiles para CI: makemigrations --check sale con código distinto de cero si hay cambios de modelo sin migración, alembic check hace lo mismo desde la versión 1.9.0, y prisma migrate status devuelve 1 si hay migraciones sin aplicar, fallidas o un historial que no cuadra con la base.

Qué hacer según el resultado

La decisión depende de una sola pregunta: ¿esa migración ya se ha aplicado en alguna base que no sea desechable? Usa este árbol:

  1. La migración editada es nueva de tu rama, no se ha subido y solo existe en tu base local. Puedes editarla o regenerarla. Revierte su aplicación local (o recrea tu base de desarrollo) y vuelve a aplicarla.
  2. Ya está en main o se aplicó en staging, CI persistente o producción. Restaura el archivo original con git restore --source=origin/main -- ruta/de/la/migracion y pide al agente que genere una migración nueva con el cambio. Es lo que Prisma recomienda: arreglar la causa restaurando el archivo, no resetear.
  3. El agente renombró o borró una migración aplicada. Mismo tratamiento: restaura el original. Si el objetivo era limpiar historial, eso es un squash planificado, no una edición (ver la última sección).
  4. Prisma te propone resetear y la base tiene datos que importan. No resetees. Restaura el archivo y vuelve a ejecutar prisma migrate status para confirmar que el checksum vuelve a coincidir.

Un prompt que suele bastar para el punto 2, como plantilla: "No modifiques migraciones existentes. Restaura X desde origin/main y crea una migración nueva con el comando del framework que añada Y. Enséñame el diff antes de aplicarla".

El check de CI que lo bloquea antes del merge

Revisar a mano funciona hasta el día que no miras. Este script convierte la comprobación anterior en un paso de CI que falla si la rama altera migraciones existentes. Ajusta las rutas a tu proyecto; requiere que el checkout de CI tenga historial suficiente de la rama base para calcular el ancestro común:

#!/usr/bin/env bash
# Falla si la rama modifica, borra o renombra migraciones que ya existen en la base
set -euo pipefail
BASE="${BASE_REF:-origin/main}"
RUTAS=('migrations/*' '*/migrations/*' 'alembic/versions/*')
TOCADAS=$(git diff --name-only --diff-filter=DMR "$BASE"...HEAD -- "${RUTAS[@]}")
if [ -n "$TOCADAS" ]; then
  echo "Migraciones existentes alteradas:" >&2
  echo "$TOCADAS" >&2
  exit 1
fi
echo "OK: la rama solo añade migraciones nuevas."

El patrón */migrations/* también cubre prisma/migrations/. Cuando necesites saltártelo a propósito (por ejemplo, en un squash), hazlo de forma explícita, con una etiqueta en el PR o un paso aprobado por otra persona, no desactivando el check.

Cierra la puerta también dentro del agente

El check de CI es la red final; conviene que el agente ni siquiera llegue ahí. Hay tres capas, de más blanda a más dura.

1. Instrucción en el archivo de contexto. Una línea en CLAUDE.md o AGENTS.md: "Las migraciones existentes son inmutables. Cualquier cambio de esquema va en una migración nueva generada con el comando del framework". Ayuda, pero es una instrucción, no un bloqueo.

2. Permisos en Claude Code. Las reglas de Read y Edit usan patrones estilo gitignore, y según esa documentación se aplican a las herramientas de edición del agente, a comandos de archivo que Claude Code reconoce en Bash (como sed o tee) y a las redirecciones. Esta configuración en .claude/settings.json hace que te pregunte antes de tocar cualquier migración:

{
  "permissions": {
    "ask": [
      "Edit(./**/migrations/**)",
      "Edit(./alembic/versions/**)"
    ],
    "deny": [
      "Bash(npx prisma migrate reset:*)"
    ]
  }
}

Se usa ask y no deny porque a veces sí quieres que el agente escriba una migración de datos a mano en un archivo nuevo. Ten en cuenta que un comando del framework que genera archivos, como makemigrations, escribe desde su propio proceso, y la documentación solo enumera comandos de archivo concretos y redirecciones; no cuentes con que estas reglas cubran cualquier script.

3. Protección propia de la herramienta. Prisma incluye una salvaguarda: si un agente como Claude Code, Cursor o Gemini CLI intenta prisma migrate reset --force, la ejecución se bloquea hasta que das consentimiento explícito, tras el cual se fija la variable PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION. Es útil, pero protege el reset, no la edición silenciosa del archivo.

Cuándo no aplica esta regla

La inmutabilidad importa cuando la migración ya vive en una base que no puedes tirar. Hay casos legítimos en los que editar o borrar migraciones es correcto:

  • Prototipo sin despliegues ni compañeros. Si la única base es tu local desechable, recrearla es más barato que acumular migraciones correctivas.
  • Migración creada en tu rama y aún sin compartir. Rehacerla antes de abrir el PR deja un historial más limpio.
  • Squash planificado. Django documenta un proceso en dos fases: primero conviven las migraciones antiguas y la condensada, y solo se borran las antiguas cuando todas las instancias las han aplicado. Ese borrado sí es intencionado y debe pasar por una excepción explícita del check.
  • Proyectos que no usan migraciones versionadas (por ejemplo, esquemas sincronizados directamente desde el modelo en desarrollo). Aquí el riesgo es otro y este diagnóstico no te sirve tal cual.

Preguntas frecuentes

¿Y si el agente edita una migración aplicada solo para corregir un comentario?

En Django y Alembic no tendrá efecto en la base, así que el riesgo real es bajo, pero el check de CI lo marcará igual. En Prisma cambia el checksum y migrate status lo reportará como modificada, así que tampoco merece la pena: déjalo como está.

¿Basta con que los tests pasen en CI?

Solo si CI aplica las migraciones sobre una base que ya tenía el historial anterior. Si CI crea la base desde cero en cada ejecución, reproduce justo el caso que funciona y no detecta el problema. Por eso el check se basa en git y no en los tests.

Compartir X LinkedIn