Cómo Claude Fable ayudó a pulir sqlite-utils 4.0rc2 y descubrió un bug crítico

Para acelerar la revisión final de sqlite-utils 4.0, el autor utilizó un modelo de IA (Claude Fable) y encontró un bug crítico en delete_where() que podía provocar pérdida de datos. Tras 37 prompts y 34 commits se publicó una RC con cambios sustanciales en manejo de transacciones.

Por Redaccion TD
Cómo Claude Fable ayudó a pulir sqlite-utils 4.0rc2 y descubrió un bug crítico

Resumen: IA como copiloto para una liberación crítica

En la preparación de sqlite-utils 4.0 el autor recurrió a Claude Fable (suscripción Max) para acelerar la revisión final y buscar problemas que podrían convertir una corrección tardía en un breaking change. El experimento costó aproximadamente 149.25 dólares y produjo una revisión intensiva que terminó en la versión 4.0rc2. El resultado: se detectaron fallos graves en el manejo de transacciones, se aplicaron correcciones en decenas de archivos y se documentó el nuevo modelo de transacciones.

Qué ocurrió: el bug que podía provocar pérdida de datos

Uno de los hallazgos más importantes fue un bug en Table.delete_where() que dejaba la conexión en un estado “in_transaction=True” y, por tanto, envenenaba la conexión: métodos posteriores entraban en un flujo de savepoint que no llegaba a hacer commit. En la práctica, eso podía borrar cambios al cerrar la conexión.

El autor reprodujo el fallo con un ejemplo sencillo que demuestra cómo una llamada a delete_where() no comprometía correctamente el estado de la transacción. El caso reproducido fue este:

db = sqlite_utils.Database("dw.db")
db["t"].insert_all([{"id": i} for i in range(3)], pk="id")
db["t"].delete_where("id = ?", [0])  # conn.in_transaction ahora es True
db["t"].insert({"id": 50})
db["u"].insert({"a": 1})
db.close()
# Al reabrir: filas [0, 1, 2] — el delete, la fila 50 y la tabla u han desaparecido

Ese comportamiento es especialmente peligroso porque, a simple vista, el código parece correcto y la pérdida ocurre al cerrar la base o en interacciones posteriores.

Proceso de corrección con ayuda de IA

Durante la fase de trabajo se ejecutaron 37 prompts que resultaron en 34 commits y en un diff total de +1,321 -190 en 30 archivos. El autor describe cómo, en tareas complejas, el agente necesitaba 10–15 minutos para procesar cada paso, lo que le permitió continuar con actividades externas (incluso asistir a un desfile del 4 de julio en Half Moon Bay) y regresar periódicamente a guiar la siguiente iteración desde el teléfono.

Los cambios se completaron mediante una revisión final en la interfaz de Pull Request de GitHub, donde se consolidaron las correcciones y se afinó la documentación. La mayor parte del trabajo se concentró en el manejo de transacciones, que fue la característica central añadida en la RC anterior.

Nuevo modelo de transacciones: qué cambió y cómo se documentó

La versión 4.0rc2 consolida un modelo consistente: cada operación que modifica la base (insert, upsert, update, delete, delete_where, transform, create_table, create_index, enable_fts, etc.) se ejecuta dentro de su propia transacción y se confirma (commit) antes de devolver el control. En la práctica, esto significa que los cambios quedan guardados en disco cuando la llamada al método termina. Lo mismo aplica para sentencias SQL ejecutadas con db.execute(): una instrucción de escritura se confirma de inmediato.

Hay dos excepciones donde debemos pensar explícitamente en transacciones:

  • Cuando se quiere agrupar varias operaciones de escritura para que todas tengan éxito o todas fallen: para eso existe db.atomic().
  • Cuando el desarrollador gestiona la transacción manualmente con db.begin(): en ese caso la librería respeta la transacción abierta y no hace commits automáticos.

Esta claridad en la documentación fue parte importante de la RC: revisar los cambios en la documentación fue un atajo útil para entender la intención del diseño antes de inspeccionar el código.

Compatibilidad con autocommit en Python 3.12

Otra sorpresa surgió al considerar el nuevo parámetro autocommit introducido en Python 3.12. La librería asume el modo de transacciones por defecto de Python; conexiones creadas con sqlite3.connect(…, autocommit=True) o autocommit=False se comportan distinto respecto a commit() y rollback().

El autor no había previsto este detalle y al correr pruebas con esa configuración la mayor parte del test suite falló. Con la ayuda del modelo se trabajó para que las diferencias en el comportamiento de conexiones con autocommit no rompieran la lógica de sqlite-utils.

Revisión cruzada con otro modelo (GPT-5.5)

Tras el trabajo inicial con Claude Fable, se realizó una revisión adicional con otro modelo (GPT-5.5) para buscar problemas residuales y comprobar que el changelog estaba actualizado. Ese pase encontró dos problemas importantes relacionados con db.query():

  1. db.query() rechazaba sentencias que no retornan filas solo después de ejecutar db.execute(), y además las escrituras quedaban auto-commitadas antes de que la comprobación lanzara el error. Es decir, db.query(“update …”) podía terminar realizando el update y luego lanzar ValueError.

  2. En casos de INSERT … RETURNING ejecutados con db.query(), el commit sólo ocurría después de que el generador devuelto se consumiera por completo. Si no se iteraba el generador (por ejemplo, usando next sobre el resultado), la transacción quedaba abierta y la escritura podía ser revertida al cerrar la conexión. Esto contradictaba la documentación que afirmaba que el efecto aplicaba sin necesidad de iterar.

Esos hallazgos fueron replicados por Claude Fable en una nueva sesión y se incorporaron a la lista de issues a corregir.

Lecciones para equipos y tomadores de decisión en Latinoamérica

El caso de sqlite-utils 4.0rc2 ilustra dos puntos relevantes para equipos de software en América Latina:

  • Los modelos de IA pueden acelerar revisiones de código y detección de errores sutiles, especialmente en áreas donde el comportamiento de la plataforma (como manejo de transacciones) puede variar según versiones de librerías o del lenguaje. El retorno de inversión puede ser atractivo para tareas críticas de estabilidad.

  • No es una solución mágica: el flujo empleado combinó la asistencia del modelo con revisiones humanas (PRs, pruebas locales, ejemplos reproducibles). La interacción humana sigue siendo necesaria para interpretar riesgos y desplegar correcciones en producción.

En entornos con recursos limitados, usar IA como copiloto para revisiones puntuales —por ejemplo, antes de una liberación mayor— puede reducir riesgo operativo sin necesidad de incorporar grandes equipos de QA adicionales.

Conclusión

El uso de Claude Fable permitió descubrir y corregir fallos serios en sqlite-utils 4.0 antes del lanzamiento estable. La experiencia muestra cómo la IA puede complementar el trabajo del desarrollador: identificar casos de borde, proponer correcciones e incluso generar documentación más clara. Aun así, la revisión humana, las pruebas y la validación en las condiciones reales de despliegue son imprescindibles para garantizar la fiabilidad de la librería.

Para quienes administran proyectos o infraestructuras locales, la recomendación es aprovechar modelos de IA como parte del flujo de revisión, pero mantener controles manuales y pipelines de pruebas robustos para validar cambios en distintos entornos y versiones de dependencias.

Fuente original: Simon Willison