BookinglyTech News
Software

AG-UI: una PR, dos artefactos de CI y la comparación que no probaba nada

Un colaborador de AG-UI cambió su lógica de cancelación tras una revisión. La prueba que iba a respaldar el arreglo se comparó contra el artefacto equivocado.

3 min de lecturaDev.to0 vistas

Un colaborador de AG-UI —el protocolo de interacción agente-usuario, la capa que lleva los eventos de un agente hasta el frontend— reescribió parte de la lógica de cancelación después de que una revisión destapara un agujero. El arreglo está en la PR #2354 y sustituye un booleano compartido por un token de cancelación por ejecución: dieciséis líneas nuevas en el fichero fuente más dos pruebas. Lo interesante es cómo se verificó, porque la primera comprobación que se hizo no probaba nada.

El fallo y el arreglo

La PR cerraba un problema que otro había abierto como issue #2300: un stream que termina sin evento terminal se resuelve como éxito, así que un run truncado es indistinguible de uno completado. El arreglo afirma que llegó un evento terminal, con excepción para los runs que el usuario canceló. Esa excepción leía un booleano del agente: abortRun() lo ponía y el siguiente run que arrancaba lo limpiaba. Basta cancelar un run y, mientras su transporte se desmonta, arrancar otro: el segundo limpia la bandera y el primero acaba reportando un truncamiento que no existe.

El alcance es más estrecho de lo que suena, y la propia revisión lo dejaba claro. HttpAgent sintetiza un RUN_ERROR cuando su fetch aborta, y eso corta la comprobación antes de leer la bandera; otras dos integraciones terminan sus streams de una forma que también se la salta. Lo que se podía demostrar eran subclases de AbstractAgent cuyo stream completa, en vez de fallar, algún tiempo después del abort.

Una verificación que no podía fallar

Antes de decir que un arreglo funciona hay que ejecutar lo que se rompía. Se instaló el cliente publicado, se corrieron los dos casos y ambos pasaron. Confirmación limpia. Inútil: el arreglo estaba en una PR abierta y el cliente publicado no lo lleva, así que ese build no tiene la aserción de evento terminal y no había nada que pudiera dispararse. Los dos casos pasaron porque ninguno de los dos existía en ese binario.

El control correcto era la historia de la propia PR, no la versión publicada: el commit tal como estaba en la revisión, 1bda0cf5, contra el commit posterior al arreglo, 077cdcf7. El CI publica un artefacto instalable por sha, así que los dos estaban a un npm install de distancia. Con esos, el «antes» sí falla —AGUIError, stream terminado sin RUN_FINISHED ni RUN_ERROR— y el «después» resuelve.

Y ahí vino el error de frase. La comparación entre los dos commits es real, pero decir que el diff de +16/-10 en agent.ts es la única diferencia entre las dos ejecuciones es falso. Lo que se instaló fueron dos artefactos, no dos commits, y ahí no están cerca: versión de paquete 0.0.58 frente a 1.0.1, zod ^3.22.4 frente a ^3.25.76, dist/index.js de 65.523 frente a 82.982 bytes. El workflow publica en push y pull_request, y su checkout no fija ref, así que en una PR construye refs/pull/2354/merge. Los dos commits están a 21 días de distancia y la 1.0.0 salió en la rama base en medio. Dieciséis líneas no añaden 17 KB de bundle.

La dirección del resultado sí sobrevive, y conviene ser exacto sobre por qué. Los dos artefactos contienen la aserción, así que el «antes» esta vez sí podía fallar, y el mecanismo cambió como dice el commit: runAborted aparece siete veces en el bundle anterior y ninguna en el posterior, mientras el token por ejecución aparece cero y luego siete. La medición se sostiene. Lo que murió fue la palabra «solo».

Para quien revise PRs ajenas apoyándose en el CI, la consecuencia es directa: comparar artefactos no es comparar commits, y un build de pull request puede arrastrar semanas de trabajo y un salto de versión mayor que no tienen nada que ver con el cambio que se quiere medir. Era la tercera vez en tres días que a este revisor le pasaba algo parecido.