En el artículo anterior pasé la revisión de commits de Opus a Sonnet y recorté el costo un 62 %. Quedó abierta una pregunta: cuántos bugs se pierden por el camino. Sin un set de referencia no hay forma de responderla, así que armé uno para el code review con IA a partir del historial de mis propios repositorios: 18 commits que esconden 30 bugs, corregidos más tarde a mano. Sobre ellos pasé el mismo script de revisión en dos modos.
Sonnet encontró 18 bugs de 30 por $11,68. Opus encontró 17 por $26,03.
Lo principal:
- Opus no encontró más en este set. Con 30 bugs, la diferencia de uno no significa nada, pero tampoco hay todavía motivo para pagar el doble.
- Los modelos se saltaron cosas distintas. Juntos encontraron 21 bugs; por separado, 17 y 18.
- Dos de los nueve bugs que se les escaparon a ambos los detecta
tsc. Gratis y en segundos. - Los demás fallos se parecen entre sí: el bug solo se ve junto a un archivo que no está en el diff, o conociendo cómo se comporta una biblioteca en tiempo de ejecución.
- La mitad de los candidatos que halló la búsqueda automática no eran bugs. Sin etiquetado manual, el set de referencia miente.
De dónde sacar bugs con respuesta conocida#
Los proveedores de revisión con IA se miden a sí mismos. En DeepSource lo dicen sin rodeos: cada uno corre su benchmark con su propio conjunto de datos y gana en él. Yo necesitaba una respuesta sobre mis scripts y mi código.
La fuente ya estaba en git. Cada commit con fix en el mensaje corrige líneas que alguien escribió antes. git blame sobre esas líneas, en el estado previo al fix, señala el commit donde nació el bug. La técnica se llama SZZ, por los apellidos de los autores de un artículo de 2005: Śliwerski, Zimmermann y Zeller.
git show -U0 --format= "$FIX" -- "$FILE" | grep '^@@'
git blame --porcelain -L 41,41 "$FIX^" -- "$FILE" | head -1Un script en Python recorrió siete repositorios: dos sitios en Next.js, un catálogo con base de datos en Prisma, una calculadora web, una wiki open source en TypeScript, una app de iOS y una app para Apple Watch en Swift. Encontró 125 pares «commit con bug → fix».
Después vino el trabajo manual. Descarté los fixes de datos y de textos y me quedé con 37 candidatos con cambios de código. Cada uno lo etiquetó un agente aparte en Sonnet: leyó el fix y el commit original y respondió tres preguntas. ¿Es un defecto real y no un requisito nuevo? ¿Está en ese commit? ¿Pudo notarlo un revisor atento sin ejecutar la app en un dispositivo? Los casos con varios bugs los revisé yo.
De los 37 etiquetados quedaron 18. El filtrado resultó más útil de lo que esperaba:
- una parte de los «fixes» implementaba un requisito nuevo, por ejemplo sincronizar el tema entre dispositivos;
- en otra parte el bug había nacido en un commit distinto, y SZZ señalaba a uno vecino que solo tocaba las mismas líneas;
- en otra el defecto apareció después, con datos que en el momento del commit todavía no existían.
Queda un detalle sin el cual las cifras no valen nada. Si al revisor se le da una copia de trabajo con el historial completo, git log --all le muestra el fix futuro con la descripción del bug. Por eso monté cada caso como un repositorio aparte de dos commits: el estado previo al bug y el commit con el bug. El futuro no está ahí.
Cómo hice la prueba#
La revisión la hizo el mismo change-review que uso todos los días. Le añadí tres argumentos: dónde está el repositorio, a qué archivos limitar la revisión y no escribir estado. Los modos son dos:
| Modo | Reconocimiento | Búsqueda y verificación de hallazgos |
|---|---|---|
| normal | Haiku | Sonnet |
thorough | Haiku | Opus |
Tras la revisión, un agente juez comparaba los hallazgos con la referencia: misma causa y mismo fallo, el número de línea puede variar. Los hallazgos que no coincidían con la referencia, el juez los comprobaba contra el código y marcaba si eran defectos reales.
Resultados del code review con IA#
| Sonnet | Opus | |
|---|---|---|
| Encontrados de 30 | 18 | 17 |
| high, de 14 | 9 | 9 |
| medium, de 10 | 7 | 6 |
| low, de 6 | 2 | 2 |
| Casos con al menos un hallazgo de la referencia | 10 de 18 | 11 de 18 |
| Hallazgos de más, y cuántos reales según el juez | 77 / 57 | 75 / 57 |
| Costo de la revisión a precio de API | $11,68 | $26,03 |
| Costo por caso | $0,65 | $1,45 |
Los precios los calculé a partir de message.usage en los registros de los agentes, igual que en el artículo anterior. El juez costó otros $1,63 y $1,70, y el etiquetado de la referencia, $3,94. El experimento entero salió en unos $45.
De los 14 bugs graves, cada modelo encontró nueve, pero no los mismos. Opus detectó un paywall que promete un periodo de prueba a un suscriptor que ya lo usó. Para verlo hay que saber que en StoreKit la elegibilidad para la oferta introductoria se comprueba aparte de la oferta misma. Sonnet detectó la configuración inicial de la wiki, que podía completar cualquier visitante de un servidor recién desplegado y quedar como administrador. En total, Sonnet tiene cuatro bugs que Opus no tiene, y Opus tiene tres que Sonnet no tiene.
Los dos modelos juntos encontraron 21 bugs, más que cualquiera por separado. Cuestan $37,71, más que Opus solo. Más barato sería correr Sonnet dos veces, por unos $23, pero esa corrida no la hice. Y una salvedad: cada modo lo ejecuté una sola vez. Parte de la diferencia puede ser la dispersión entre corridas del mismo modelo, y eso todavía no lo medí.
Lo que no encontró nadie#
Nueve bugs se les escaparon a los dos modelos. Se dividen en tres grupos.
Errores de tipos. En la calculadora se llama a una función sin el parámetro obligatorio weight, y en otro archivo se usa un tipo IntlMessages que no está declarado en ningún sitio. En ambos commits la compilación fallaba. Los revisores, sin embargo, encontraron en esos mismos archivos entre 6 y 10 problemas distintos y pasaron por alto justo estos dos. tsc --noEmit lo detecta sin gastar un solo token. De ahí el orden: primero compilador, tests y linter, y solo después el modelo.
El bug vive en la unión con otro archivo. Tres casos:
- de la CSP quitaron
'unsafe-eval', pero el MDX se ejecuta en el cliente connew Function(), y los artículos se caen en producción; serialize()compila el MDX en modo dev para un runtime de JSX distinto del que luego lo renderiza;- el mapa del sitio se prerrenderiza al construir la imagen de Docker, donde no hay base de datos, y devuelve una lista vacía durante seis horas seguidas.
En el diff hay cada vez un solo archivo, y por sí solo se ve bien. Para ver el bug hay que abrir el componente que usa esa configuración o saber dónde se ejecuta el código. El revisor tiene permiso para leer el código de alrededor, pero con un diff acotado no lo hace. Es el mismo punto ciego del que hablé la vez anterior: una pasada incremental archivo por archivo no ve las relaciones entre archivos.
Menudencias. Tres bugs de nivel low, discutibles, y una excepción para slug === "test" cuyo sentido solo se explica en el mensaje del mismo commit.
Cuánto cuesta una evaluación#
Las dos corridas juntas consumieron unos 64 millones de tokens. Entre el 82 y el 85 % es lectura de caché, que cuesta veinte veces menos que la entrada normal. La mecánica es la misma que en el artículo anterior. Cada agente arranca con un contexto de unos 49 000 tokens, y las solicitudes lo releen una y otra vez. En 18 casos son unos 130 agentes por modo.
Buena parte de esos 49 000 no tiene que ver con la tarea: las reglas generales de CLAUDE.md y la memoria del proyecto desde el que lancé la evaluación. Los agentes revisaban repositorios ajenos, pero cargaban con las reglas de mi sitio. Por eso una de las revisiones llegó a encontrar en un commit antiguo una firma de asistente que mis propias reglas prohíben.
Qué voy a cambiar#
- El modo normal se queda en Sonnet. No hay datos a favor de Opus en la revisión.
thoroughqueda para los releases, como segunda pasada independiente después de la normal. - Antes de la revisión,
tscy la compilación. Dos de los nueve fallos se cierran gratis. - Al revisor, los archivos que usan el código modificado. Hoy solo recibe el diff. La lista de quienes lo llaman se obtiene con grep antes de lanzar el modelo.
- La referencia ahora vive junto a los scripts. Cualquier cambio en el prompt de revisión se puede correr sobre los mismos 18 casos para ver si mejora o empeora.
Lo que no comprobé#
- Dispersión entre corridas. Cada modo lo ejecuté una vez. Para decir «los modelos se saltan cosas distintas» y no «una corrida se saltó cosas distintas», hacen falta al menos tres corridas por modo.
- La referencia la etiquetó Sonnet. Revisé los casos con varios bugs, pero no todos. Si el etiquetador pasó por alto un bug en un commit, no entró en la referencia, y el hallazgo del revisor fue a parar a «de más».
- El juez también es Sonnet, con nivel de esfuerzo bajo. Su valoración de los hallazgos de más (57 reales de 77) no la revisé a mano.
- 30 bugs son pocos. Una diferencia de menos de 3 o 4 bugs entre modos es ruido en un set así.
Si tienen sus propios scripts de revisión, empiecen por una tarde: SZZ sobre el historial, etiquetado manual, casos aislados. Sin eso, cualquier «el modelo nuevo revisa mejor» es una opinión, no una medición. Cómo están hechos los scripts de workflow lo conté en el artículo sobre workflows de Claude Code. De dónde sale su consumo de tokens, en el artículo sobre el consumo de tokens de Claude Code.



