Intro: el gate que no deja decir «listo»
Un gate, tal como lo usa Shopify, es una comprobación que un cambio escrito por IA debe superar antes de avanzar. Reconstruidos en Claude Code, solo el primero valió la pena: los tests de aceptación. Shopify publicó cuatro gates y ningún número, así que este experimento los construye sobre un repositorio real, ejecuta cincuenta peticiones con cada gate desactivado por turnos y cuenta qué detectó cada uno y cuánto costó.
Helix se hizo público el 21 de septiembre de 2026. Hace pasar cada checkpoint por cuatro gates, en orden: tests, revisión de diseño, dos revisores y un ingeniero. El post no da ninguna tasa de aciertos. Los revisores son la parte que más gente copia, y en estas cincuenta ejecuciones la tasa de aciertos solo se movió cuando los tests formaban parte del bucle. Los revisores no aportaron nada, a un coste de aproximadamente el triple.
Del mismo banco de pruebas salen otros dos hallazgos. Un hook que supuestamente no se puede saltar tiene tres salidas en su propia documentación. Y cuando los tests y los revisores no estaban de acuerdo, el agente hizo algo que nadie había previsto. El recuento empieza por el caso sin ningún gate.
Cuatro gates, cero números, un banco de pruebas
El repositorio del banco de pruebas es tinydb, una base de datos de documentos real, con 226 tests en verde en el commit utilizado.
Helix es la herramienta interna de Shopify para devolver sus apps móviles al código nativo. Su post enuncia la regla sin rodeos: cada checkpoint pasa por cuatro gates, en orden.
| Gate | Qué hace en el post de Shopify |
|---|---|
| Comportamiento | Ejecuta tests en los que todos los casos relevantes deben pasar |
| Diseño | Pide a un segundo modelo que actúe como un revisor de diseño perfeccionista |
| Revisión | Dos agentes revisores independientes y con contexto aislado; el bucle se repite hasta que ambos aprueban |
| Ingeniero | Una persona cierra el bucle |
La frase que vende el diseño es «An attempt is allowed to be wrong. It is not allowed to ship until it isn't.» El gate de diseño se descarta en una librería, y el post dice que un cambio de pura lógica simplemente lo salta.
Lo que el post nunca da es un número. La app Shop se reconstruyó en 12 semanas y la app de Shopify tiene más de 300 pantallas, y ese es todo el balance. No hay tasa de aciertos, ni coste, ni recuento de defectos que se hayan escapado.
El banco de pruebas son cinco peticiones escritas como las escribe un desarrollador: operaciones push y pull, una consulta between, el cambio de nombre de una tabla, una búsqueda ordenada y un tamaño de caché de escritura. Detrás de cada petición hay cinco requisitos ocultos que el agente nunca ve, las cosas que esperaría un ingeniero sénior cuidadoso. Eso hace 25 requisitos en total.
El trabajador es Sonnet 5.5, ejecutado en modo headless. Cinco configuraciones por cinco tareas por dos repeticiones dan 50 ejecuciones. Hay una salvedad: las versiones explícitas de estas peticiones pasan de una sola vez, así que los prompts se acortaron a lo que la gente escribe de verdad.
Sin ningún gate, pasan 35 de los 50 requisitos. Eso es un 70%, con tres ejecuciones de cada diez totalmente limpias.
La tarea de ordenación muestra cómo son los fallos. El agente indexó todos los documentos por el campo de ordenación, y en cuanto a un documento le falta ese campo, toda la búsqueda se cae. Cinco requisitos, cero superados, una clave ausente.
El hook que se niega a dar por terminado y sus tres salidas
Un Stop hook es un script que Claude Code ejecuta cada vez que el agente intenta terminar su turno. La propia guía de Anthropic nombra el problema que aborda: Claude se detiene cuando el trabajo parece terminado. Sin una comprobación que pueda ejecutar, «parece terminado» es la única señal disponible, y tú te conviertes en el bucle de verificación.
El hook vive en el archivo de configuración del proyecto, bajo hooks, Stop, como un único comando con un timeout. Aquí es un wrapper de una línea que llama al script del gate. El contrato es el código de salida: 0 deja terminar el turno, 2 lo bloquea, y todo lo que el script haya imprimido en la salida de error estándar vuelve al agente como motivo.
En la práctica funciona así. El agente resume su cambio y se detiene. El hook ejecuta los tests de aceptación, uno falla y el turno no termina. El agente lee el fallo como feedback del hook, en este caso un test que esperaba un error de tipo y no lo obtuvo, corrige el código y se detiene de nuevo. Esta vez el gate no dice nada.
Una regla en el prompt es un consejo. Un hook no. Aun así, la misma página de documentación enumera tres formas de salir.
| Salida | Lo que dice la documentación |
|---|---|
| El tope | Después de que los stop hooks hayan continuado el turno 8 veces seguidas, Claude Code anula el siguiente bloqueo y termina el turno. El contador se reinicia cada vez que el agente llama a una herramienta, y una variable de entorno lo eleva. |
| El timeout | 600 segundos por defecto para un hook de tipo comando, y un hook que agota el timeout no emite ninguna decisión. Una suite de tests lenta abre el gate por sí sola. |
| El flag | La entrada del hook lleva stop_hook_active, verdadero cuando la sesión ya está continuando por culpa de un stop hook. La documentación dice que lo compruebes para no bloquear nunca por una condición que no puede resolverse. |
El gate de este experimento registra ese flag y lo ignora. Se rinde por sí solo después de seis bloqueos, así que los bucles se pueden contar.
Hay una salida más que nadie documenta. Tras ser bloqueado una vez, el agente se puso a buscar los tests: ejecutó cat sobre el script del hook y buscó el archivo de tests por todo el disco. Solo lo detuvo la lista de permitidos. Por eso los tests de aceptación viven fuera del árbol del repositorio, la configuración deniega la lectura del hook y de los tests ocultos, y el agente ve el fallo pero nunca el archivo.
Gate 1: los tests de aceptación
El gate de tests de aceptación es un conjunto de tests, escritos de forma independiente del implementador, que definen qué significa «terminado». El primer gate de Shopify no son los tests del propio agente. Un subagente genera casos de test de estilo integración por cada checkpoint, empujando hacia casos límite más allá del camino feliz, y esos tests definen qué es lo demostrado.
Por eso el escritor aquí es un Sonnet headless aparte. Ve la petición y el repositorio antes del cambio, en solo lectura, no puede ejecutar código, y se le indica que escriba un test por cada comportamiento que esperaría un sénior cuidadoso: campos ausentes, tipos incorrectos, límites, persistencia, la caché y el comportamiento existente sin cambios. Escribe entre 23 y 53 tests, 39 de media, en unos 40 segundos. El archivo queda fuera del repositorio, así que cuando un test falla el agente lee la aserción y nunca el test.
Solo con los tests, la tasa de aciertos pasa del 70% al 84%: 42 de 50 requisitos, y seis ejecuciones totalmente limpias en lugar de tres.
| Tarea | Sin tests | Con tests |
|---|---|---|
| Búsqueda ordenada | 5 de 10 | 10 de 10 |
| Tamaño de la caché de escritura | 8 de 10 | 10 de 10 |
| Push y pull | 4 de 10 | 4 de 10 |
El fallo por campo ausente en la tarea de ordenación es exactamente el tipo de caso por el que pregunta el escritor. En la tarea de la caché, el escritor exigió un error de valor para un tamaño de caché menor que uno.
La tarea de push y pull no se mueve. Se le escapan tres requisitos, dos de ellos sobre un documento sin el campo, y la petición no decía nada al respecto. El escritor duda justo donde el prompt calla: su test acepta una excepción o un no-op silencioso, así que el gate no puede recuperar un requisito que nadie enunció.
En las 30 ejecuciones con gate y tests, el gate devolvió al agente 28 veces con un requisito real fallando, y cinco veces con todos superados. Uno de esos cinco fue la tarea de cambio de nombre: el escritor quiso que se rechazaran los nombres que no son cadenas, nadie lo había pedido, y el agente perdió una ronda por eso.
La primera versión de este gate volvía a ejecutar los tests del propio implementador. Dejaba pasar todas las ejecuciones en su primera parada mientras un requisito oculto fallaba, porque los tests de un agente prueban lo que ese agente construyó.
El precio del gate es de $0.20 por ejecución en lugar de $0.085, y 60 segundos en lugar de 33.
Gate 3: dos revisores, tal como lo escribió Shopify
El gate de revisión es una pareja de agentes revisores, cada uno trabajando en un contexto nuevo. Las palabras de Shopify: dos agentes revisores independientes y con contexto aislado comprueban el código nuevo, hay que corregir todos los hallazgos, y el bucle se repite hasta que ambos revisores aprueban. La guía de Anthropic explica por qué importa el aislamiento: un revisor en un contexto nuevo ve solo el diff y los criterios que le das, no el razonamiento que produjo el cambio.
Cada revisor aquí es un Sonnet headless con herramientas de solo lectura y como máximo 15 turnos. Devuelve un veredicto como JSON estructurado: un flag approve y hallazgos, cada uno con una severidad de blocking o minor. Dos revisores se ejecutan en paralelo sobre la petición y el diff completo, sus hallazgos se combinan, y el gate bloquea cuando alguno de los dos encuentra algo bloqueante. Una pareja cuesta entre 3 y 6 centavos por parada.
| Configuración | Requisitos superados | Tasa de aciertos | Bloqueos |
|---|---|---|---|
| Sin gate | 35 de 50 | 70% | ninguno |
| Solo revisores | 34 de 50 | 68% | 1 de cada 10 ejecuciones |
| Solo tests | 42 de 50 | 84% | 6 de cada 10 ejecuciones (3 correctos, 3 falsos) |
| Revisores después de los tests | 41 de 50 | 82% | 2 de cada 10 ejecuciones, ambos correctos |
Los revisores solos quedan dos puntos por debajo de no tener ningún gate, y los revisores después de los tests quedan dos puntos por debajo de los tests solos.
La tarea de ordenación explica el número. En una ejecución ambos revisores bloquearon: ordenar indexa el documento, un documento sin ese campo provoca un error de clave, y toda la búsqueda falla. El agente lo corrigió y pasó cinco de cinco.
En otras dos ejecuciones ambos revisores vieron exactamente el mismo fallo y lo calificaron de menor. Un documento sin el campo de ordenación provoca un error de clave simple, y el docstring documenta este comportamiento. Ambos revisores aprobaron, y las dos ejecuciones terminaron en cero de cinco. Mismo modelo, mismo diff, mismo bug, dos severidades distintas. Los revisores nunca se equivocaron cuando bloquearon. El problema es lo que dejan pasar.
Un paper de agosto midió la misma forma en un benchmark público. Zero-shot obtuvo 77%, un revisor 77%, y dos revisores independientes 75%, todo dentro del ruido respecto a no revisar nada.
| Configuración en LiveCodeBench | Puntuación |
|---|---|
| Zero-shot | 77% |
| Un revisor | 77% |
| Dos revisores independientes | 75% |
| Revisor más crítico (revisión adversarial) | 87% |
Revisor más crítico, y el bucle que nunca termina
La revisión adversarial es un revisor emparejado con un crítico que audita la revisión mediante un desacuerdo estructurado antes de que el agente principal edite. El mismo paper encontró que es la forma que funciona. Obtuvo 87% donde dos revisores obtuvieron 75%, y superó a la mejor referencia de cinco agentes, que tenía 82%.
| Benchmark | Zero-shot | Con revisión adversarial | Coste |
|---|---|---|---|
| SWE-bench | 71.6 | 75.2 | unas 4.5x los tokens |
Así que el crítico entró en el gate. El revisor A revisa. El crítico B recibe los hallazgos más el diff y sella cada uno como confirmado o rechazado. Solo un hallazgo bloqueante confirmado cierra el gate.
En este repositorio el resultado fue 39 de 50, es decir, 78%. La pareja revisor y crítico bloqueó cero veces en diez ejecuciones. Durante la validación sí rechazó un hallazgo falso que la forma de dos revisores habría bloqueado.
Lo que el crítico no pudo arreglar es el bucle. Tres ejecuciones llegaron a seis bloqueos sobre el mismo test de aceptación, y el gate se rindió con el requisito real aún fallando en cada parada. En dos de ellas el test que bloqueaba era un error del propio escritor, y el agente lo dijo: no iba a modificar el código por un test que no podía leer.
La tercera es la que hay que recordar. El test de aceptación quería que una actualización fallida dejara intactos todos los documentos, así que el agente reescribió el método de actualización de la tabla. Los revisores bloquearon eso, porque el cambio en el método de actualización no se había pedido y la tarea eran dos funciones nuevas. El agente revirtió. Su último mensaje decía que el test de aceptación y la revisión de código pedían cosas opuestas, así que se había detenido sin cambiar nada. Seis bloqueos después, el contador lo dejó salir.
El punto de rendición aquí está en seis. El tope propio de Claude Code son ocho bloqueos consecutivos y se reinicia en cada llamada a una herramienta, así que no saltó ni una vez en cincuenta ejecuciones.
El gate 4 eres tú: lo que llegó al humano, la factura, el kit
El cuarto gate es el ingeniero, y el post de Shopify promete que los gates no se vuelven más laxos cuando nadie mira. Lo que llega al ingeniero es lo que dejaron pasar los demás gates. El balance de abajo responde qué gate se pagó solo.
| Configuración | Superados de 50 | Tasa de aciertos | Coste por ejecución | Pendientes para el humano |
|---|---|---|---|---|
| Sin gate | 35 | 70% | $0.085 | 15 |
| Tests de aceptación | 42 | 84% | $0.20 | 8 |
| Solo revisores | 34 | 68% | $0.15 | 16 |
| Tests con revisores | 41 | 82% | $0.28 | 9 |
| Tests con revisor más crítico | 39 | 78% | $0.28 | 11 |
Cada fila con gate tarda de 54 a 86 segundos por ejecución, frente a 33 sin gate.
Los bloqueos en las cincuenta ejecuciones se desglosan así. Los tests devolvieron al agente 28 veces con razón y cinco veces sin ella. Los revisores bloquearon tres veces, nunca por error, y tres bucles se rindieron. Toda la matriz costó $9.95 de gasto de API a precio de lista según lo reporta la CLI, y unos 3,136 segundos, es decir, 52 minutos, de tiempo de trabajador sumado.
El veredicto para un repositorio como este: quédate con el gate de tests, con un escritor que no sea el implementador y tests que el agente no pueda editar. Espera un falso bloqueo de cada seis, y dale al bucle un contador. El gate de dos revisores tal como lo escribió Shopify no movió la tasa de aciertos aquí, en ninguna de sus dos formas. Si lo mantienes, mantenlo como consejo en el feedback, no como una barrera.
Una trampa por el camino. La documentación describe un flag bare que omite hooks, skills, plugins y memoria en las llamadas con script. Con un login de suscripción solo lee una clave de API y devuelve un error, así que cada revisor se aísla con una fuente de configuración vacía y los hooks desactivados.
Los límites son reales: un repositorio, un modelo trabajador, cinco tareas, dos repeticiones por celda. Una diferencia de dos puntos entre filas es ruido. Los catorce puntos entre no tener gate y el gate de tests no lo son. El gate de diseño no se construyó, y al ingeniero no se le cronometró.
De los cuatro gates de Shopify, en este repositorio, solo los tests valieron la pena. Todo el kit está en el pack gratuito: el archivo de configuración con el hook y la lista de denegados, el script de Stop, el gate, los comandos del escritor y de los revisores con sus esquemas, y la plantilla del balance.
AIDive