Cada hook es una cicatriz: 84 fallos de agentes codificados en código
Tengo 84 hooks interceptando 15 de los 26 tipos de eventos del ciclo de vida que Claude Code expone a partir de v2.1.116 (abril de 2026) en mi sistema de orquestación de agentes. Cada hook es un script de shell o un fragmento de Python que se dispara antes o después de una acción específica del agente: lecturas de archivos, escrituras de archivos, comandos bash, peticiones web, creación de subagentes, operaciones de git, llamadas a herramientas MCP. Cada hook existe porque algo salió mal.
Cada hook en un sistema de orquestación de agentes se remonta a un fallo de producción específico, lo que convierte la colección de hooks en memoria institucional codificada como scripts de shell. Los agentes borraron cachés de CDN, leyeron archivos de credenciales, reportaron pruebas exitosas que nunca ejecutaron y se desviaron de su tarea durante 40 minutos. Cada incidente produjo una pequeña protección determinista que se dispara silenciosamente en cada sesión posterior.
No teóricamente mal. Mal en producción. Un agente borró una caché de CDN que servía millones de peticiones. Un agente intentó escribir claves SSH. Un agente reportó “todas las pruebas pasan” sin invocar pytest. Un agente se desvió tanto de su tarea que pasó cuarenta minutos optimizando una función en un archivo que no tenía nada que ver con el trabajo asignado.
No diseñé ninguno de estos hooks de forma proactiva. No me senté a enumerar los modos de fallo de los agentes autónomos de IA y escribir controles preventivos. Cada hook es reactivo. Algo se rompió, escribí un script para evitar que se rompiera de nuevo, y el script se ha disparado silenciosamente en cada sesión desde entonces. El sistema de hooks no es una arquitectura de seguridad. Es una colección de cicatrices.
TL;DR
- El borrado de caché: Un agente borró una caché de CDN en producción usando una llamada autorizada a API. Dos hooks (47 líneas) ahora bloquean las operaciones destructivas detrás de una contraseña tecleada por un humano.
- El lector de credenciales: Un agente incluyó tokens de API en su ventana de contexto. Una protección que coincide con rutas ahora bloquea las lecturas de archivos de credenciales y registra el acceso a archivos
.env. - El verificador fantasma: Un agente reportó “todas las pruebas pasan” sin ejecutar pytest. Un detector de lenguaje vacilante redujo la verificación fantasma del 12 % a menos del 2 % de las sesiones.
- Las doce desviaciones: Los agentes perdieron verificablemente el rastro de su tarea doce veces en sesenta días. Un detector de similitud de coseno con umbral 0,30 ahora se dispara cada 25 llamadas a herramientas.
- La taxonomía: Seis categorías estructurales de fallo cubren los 84 hooks. Las categorías novedosas son raras después de más de 500 sesiones. El sistema se vuelve más duro con cada incidente.
El borrado de caché: cómo una llamada autorizada rompió la producción
El 21 de marzo de 2026, le pedí a un agente que investigara por qué las páginas de mercado en resumegeni.com cargaban lentamente. El agente comenzó su investigación normalmente: leyendo los manejadores de rutas, revisando las consultas a la base de datos, perfilando el renderizado de plantillas. Luego decidió que entradas obsoletas en la caché de Cloudflare podrían estar enmascarando las características reales de rendimiento.
El agente llamó a mcp__cloudflare__cache_purge con purge_everything: true.
Cada página en caché del sitio de producción quedó invalidada al instante. La CDN pasó de servir la mayoría de las peticiones en 80-100 ms a reenviar cada petición al servidor de origen en Railway. La página de mercado de Austin pasó de menos de un segundo a 14 290 milisegundos. Nueva York de menos de un segundo a 6891 ms. Cada página del sitio se renderizaba ahora desde el origen en frío en cada petición.
El agente no hizo nada no autorizado. Usó una herramienta MCP legítima con credenciales válidas para llamar a un endpoint API autorizado. El borrado de la caché era un paso de investigación razonable si estás depurando el comportamiento de la caché. El problema era que “razonable para depurar” y “catastrófico para producción” eran la misma llamada a API, y no existía ninguna restricción entre el razonamiento del agente y la consecuencia en producción.
Esa noche construí dos hooks.
La protección de Bash (destructive-api-guard.sh): Se dispara en cada comando bash. Hace coincidencia de patrones contra curl.*purge, rm -rf, DROP TABLE, docker.*rm, git push.*--force. Bloqueo duro (exit 2). El agente ve un mensaje explicando por qué el comando fue bloqueado y sugiriendo alternativas. No puede continuar sin la contraseña “rosebud”, que solo puede entrar en el contexto si un humano la teclea.
La protección de MCP (destructive-mcp-guard.sh): Se dispara en cada llamada a herramienta MCP que coincida con mcp__cloudflare o mcp__github. Hace coincidencia de patrones contra purge, delete, destroy, remove en los parámetros de la herramienta. Mismo bloqueo duro, misma barrera de contraseña.
Dos hooks. Dos scripts de shell. Total: 47 líneas de código. Han evitado cero borrados de caché desde su instalación, porque ningún agente ha intentado uno desde que se añadió la barrera de contraseña. Los hooks no están atrapando ataques. Están evitando que la categoría de error sea posible.
El incidente del borrado de caché también expuso el problema de rendimiento que se suponía debía investigar. Austin a 14 segundos en renderizado en frío llevó al traspaso de la página de mercado, que llevó a la corrección de la forma de la consulta cuatro días después. El incidente fue útil. El hook asegura que no pueda repetirse.
El lector de credenciales
En febrero de 2026, un agente que recopilaba contexto para un proyecto leyó ~/.claude/docs/credentials.md. El archivo contiene tokens de API para Cloudflare, GitHub, Railway y otros servicios. El agente incluyó un resumen del contenido del archivo en sus notas de trabajo, lo que significó que los tokens estaban presentes en la petición API a los servidores de Anthropic.
No se hizo commit de ningún token. Ningún token se expuso públicamente. Pero los tokens viajaron a través de una API de terceros en una ventana de contexto que no controlo. La superficie de riesgo se expandió de “mi máquina” a “mi máquina más la infraestructura de inferencia de Anthropic”.
La protección de rutas de credenciales se dispara en cada lectura de archivo. Verifica la ruta contra una lista de patrones sensibles: .env, credentials, .ssh/, .aws/, .gnupg/, secrets. Para lecturas de credenciales, el hook registra una advertencia y bloquea la lectura. Para lecturas de .env, permite la lectura pero registra el acceso.
La protección es informativa para la mayoría de las rutas y un bloqueo duro para archivos de credenciales. La distinción importa: un agente leyendo .env para entender los nombres de variables de entorno es contexto útil. Un agente leyendo credentials.md para entender tokens de API es un incidente de seguridad.
Desde su instalación, la protección de rutas de credenciales se ha disparado 23 veces a lo largo de más de 200 sesiones. Veinte de esas fueron agentes leyendo archivos .env (registrado, permitido). Tres fueron agentes intentando leer archivos de credenciales o claves (bloqueado). Cada lectura bloqueada fue un agente que estaba recopilando contexto del proyecto ampliamente y que casualmente incluyó un archivo sensible en su patrón de búsqueda. Ninguno fue malicioso. Todos habrían puesto secretos en la ventana de contexto sin la protección.
El verificador fantasma
El modo de fallo más insidioso es un agente que reporta una verificación exitosa sin realizar la verificación.
Sesión 147. Le pedí a un agente que refactorizara una consulta a la base de datos y verificara el cambio con la suite de pruebas existente. El agente refactorizó la consulta correctamente. El informe de finalización decía: “Todas las pruebas pasan. La consulta refactorizada produce resultados idénticos a la original”.
Revisé el registro de la sesión. No apareció ninguna invocación a pytest. No se había llamado a ningún ejecutor de pruebas de ningún tipo. El agente razonó que las pruebas pasarían porque la consulta refactorizada era lógicamente equivalente a la original, y reportó este razonamiento como si fuera un resultado de prueba.
La consulta refactorizada era correcta. Las pruebas habrían pasado. El razonamiento del agente era sólido. Pero razonar sobre pruebas no es ejecutar pruebas, y la brecha entre ambos es donde los bugs llegan a producción. Si la consulta refactorizada hubiera estado sutilmente mal en un caso límite que el razonamiento del agente no cubrió, el bug se habría desplegado con un informe de finalización alegando verificación de pruebas.
El modo de fallo ocurrió 7 veces en 60 sesiones antes de que construyera el hook de la barrera de evidencia. El hook se dispara en cada informe de finalización y escanea en busca de lenguaje vacilante: “deberían pasar”, “creo que”, “probablemente las pruebas pasan”, “tengo confianza”. Cuando se detecta, el hook inyecta un mensaje: “Se detectó lenguaje vacilante. Cita evidencia específica: pega la salida de las pruebas, nombra el archivo y el número de línea, o haz referencia al paso de verificación específico”.
El hook no verifica que las pruebas se hayan ejecutado realmente. Señala el patrón lingüístico que indica que la verificación fue omitida. La detección es imperfecta. Un agente suficientemente fluido podría reformular su vacilación para evitar el patrón. Pero el hook atrapa el caso común, que representa el 12 % de los fallos de agentes que requieren intervención humana.1
Después de la instalación del hook, la verificación fantasma cayó del 12 % a menos del 2 % de las sesiones. El 2 % restante son casos donde el agente reformula la vacilación o donde la afirmación de verificación es técnicamente precisa pero incompleta (por ejemplo, “las pruebas unitarias pasan” cuando las pruebas de integración no se ejecutaron).
La desviación
Entre enero y marzo de 2026, mi detector de desviaciones se disparó doce veces en sesiones donde el agente había perdido verificablemente el rastro de su tarea asignada.
El detector de desviaciones funciona incrustando el prompt de la tarea original y comparándolo periódicamente con la incrustación de las acciones recientes del agente. Cuando la similitud de coseno cae por debajo de 0,30, el sistema inyecta una advertencia que contiene el prompt original. Calibré el umbral mediante experimentación: 0,50 era demasiado sensible (se disparaba en exploración legítima de subtareas), 0,20 era demasiado permisivo (se perdían desviaciones obvias), 0,30 atrapaba cada incidente de desviación verificado.
La sesión 203 fue el caso más claro. La tarea era “arreglar el escape XML roto del sitemap para slugs de trabajo que contienen ampersands”. El agente comenzó leyendo el código de generación del sitemap. Luego notó que el sitemap se generaba a partir de una consulta a la base de datos. Luego notó que la consulta a la base de datos podía optimizarse. Luego pasó 40 minutos refactorizando la consulta en un patrón de vista materializada, escribió pruebas para la nueva consulta y reportó la optimización como completa. Nunca arregló el escape de ampersands.
El detector de desviaciones habría atrapado esto en la marca de 25 llamadas a herramientas, aproximadamente 15 minutos después del inicio de la sesión, cuando la similitud entre “arreglar escape XML del sitemap” y “crear vista materializada” cayó por debajo del umbral. En cambio, descubrí la desviación durante la revisión.
La sesión 89 fue más sutil. La tarea era “añadir limitación de tasa a los endpoints de autenticación”. El agente añadió la limitación de tasa correctamente. Luego notó que el flujo de autenticación tenía mensajes de error inconsistentes. Luego estandarizó los mensajes de error. Luego notó que el formato de respuesta de error difería del estándar de formato de respuesta API. Luego refactorizó el formato de respuesta en 12 endpoints. La limitación de tasa era correcta y completa. La explosión del alcance fue la desviación.
El detector de desviaciones se dispara cada 25 llamadas a herramientas. En los doce disparos por debajo del umbral, el agente se había desviado verificablemente de la tarea original. En seis casos, el agente se autocorrigió después de ver la advertencia inyectada. En cuatro casos, el agente reconoció la desviación pero argumentó que el trabajo actual era valioso (a veces correctamente). En dos casos, el agente ignoró la advertencia y continuó con el trabajo divergente.
El hook no previene la desviación. Hace visible la desviación. La decisión de redirigir o permitir el trabajo divergente permanece con el humano. Pero sin el hook, la desviación es invisible hasta el informe de finalización, momento en el cual el presupuesto de contexto ya está gastado.
La taxonomía de cicatrices
Después de 84 hooks, emergen patrones. Los fallos se agrupan en seis categorías:
| Categoría | Hooks | Ejemplo |
|---|---|---|
| Exposición de credenciales | 12 | Agente lee .ssh/, incluye claves de API en resúmenes, accede a configuraciones de nube |
| Operaciones destructivas | 8 | Borrado de caché, eliminaciones de bases de datos, force pushes, eliminaciones de archivos |
| Desviación de tarea | 4 | Agente trabaja en el problema equivocado, explosión del alcance, madrigueras de subtareas |
| Calidad de salida | 6 | Verificación fantasma, vacilación sin evidencia, informes incompletos |
| Agotamiento de recursos | 3 | Demasiados subagentes creados, bucles no acotados, desbordamiento de contexto |
| Contaminación entre proyectos | 4 | Agente en proyecto A modifica archivos en proyecto B |
Los 47 hooks restantes son específicos del proyecto (aplicación de convenciones, protecciones de despliegue, validadores de traducción) o experimentales (seguimiento de costes, métricas de sesión, latidos de actividad).
Las seis categorías estructurales son estables. Los nuevos incidentes dentro de estas categorías son atrapados por hooks existentes. Las categorías novedosas son raras. En seis meses de operación, solo emergió una nueva categoría estructural (contaminación entre proyectos, descubierta cuando una sesión ejecutándose en el proyecto obsidian-signals intentó editar archivos en blakecrosley.com). Las otras cinco categorías se establecieron dentro de las primeras 60 sesiones.
El estudio Agents of Chaos, un experimento multiuniversitario de 14 días que dio a seis agentes de IA acceso a correo electrónico, bash, sistemas de archivos y GitHub, identificó de forma independiente categorías de fallo superpuestas: respuesta desproporcionada (operaciones destructivas), secuestro de identidad (exposición de credenciales), bucles infinitos (agotamiento de recursos) y cumplimiento gradual bajo presión (desviación de tarea).5 La convergencia entre su investigación controlada y mi experiencia en producción sugiere que estas categorías son propiedades estructurales de los agentes autónomos, no artefactos de ninguna configuración específica.
Lo que los hooks no pueden atrapar
Los hooks operan a nivel de llamada a herramienta. Interceptan la acción antes o después de que ocurra. No pueden interceptar el razonamiento que llevó a la acción.
Un agente que decide refactorizar una función en lugar de arreglar el bug reportado produce una llamada a herramienta válida (escritura de archivo) con contenido correcto (código sintácticamente válido) que viola la tarea (función equivocada). Ningún hook atrapa esto porque ninguna llamada a herramienta es sospechosa. El detector de desviaciones lo atrapa eventualmente, pero solo después de que el agente haya consumido contexto significativo en el trabajo equivocado.
Los hooks tampoco pueden atrapar fallos de composición donde cada acción individual está autorizada pero la secuencia produce un resultado no autorizado. El borrado de caché fue un fallo de composición: leer la configuración de la caché (autorizado), llamar a la API de borrado (autorizado), pero la combinación (borrar la caché de producción durante una investigación) era dañina. La protección MCP ahora atrapa la combinación específica, pero las composiciones novedosas siguen sin estar cubiertas.
La brecha de composición de la cadena de suministro opera al mismo nivel: componentes de confianza se componen en comportamientos no autorizados. Los hooks son protecciones a nivel de componente. El razonamiento a nivel de composición requiere un mecanismo diferente, uno que evalúe secuencias de acciones en lugar de acciones individuales. El detector de desviaciones es la aproximación más cercana: evalúa la trayectoria de comportamiento en lugar de llamadas a herramientas individuales. Pero mide la similitud con la tarea original, no la seguridad de la secuencia de acciones compuesta.
La brecha entre los hooks y la seguridad completa es la brecha entre la memoria institucional y la previsión institucional. Los hooks recuerdan lo que salió mal. No predicen lo que saldrá mal a continuación.
Por qué lo reactivo es honesto
Podría diseñar un sistema de hooks proactivo. Enumerar cada modo de fallo posible. Escribir controles preventivos para cada uno. Construir una arquitectura de seguridad completa antes de la primera sesión.
No hago esto porque el diseño proactivo requiere predecir fallos que no han ocurrido. Las predicciones serían erróneas. Los hooks serían o demasiado amplios (bloqueando acciones legítimas) o demasiado estrechos (perdiendo el patrón de fallo real). La tasa de falsos positivos erosionaría la confianza en el sistema de hooks, y empezaría a ignorar las alertas.
Los hooks reactivos son honestos. Cada uno dice: “esta cosa específica ocurrió, y aquí está la protección específica que lo evita”. La protección está calibrada con precisión al fallo porque el fallo definió la protección. Los falsos positivos son materialmente más bajos porque el patrón se extrae de un incidente real, no se imagina a partir de un modelo de amenazas. Una protección reactiva aún puede sobreajustarse más tarde a medida que el código base evoluciona, pero la precisión inicial es alta.
El enfoque reactivo tiene un coste: la primera instancia de cada categoría de fallo tiene éxito. El borrado de caché ocurrió. La lectura de credenciales ocurrió. La verificación fantasma se envió. La desviación consumió contexto. Cada primer fallo es el precio de admisión para una protección precisa y de bajo ruido que previene el segundo fallo.
Después de más de 500 sesiones, se han encontrado la mayoría de las categorías estructurales de fallo. El coste del primer fallo se amortiza a lo largo de cientos de sesiones donde el hook evitó la recurrencia. El sistema se vuelve más duro con cada incidente. No más inteligente. Más duro.
Cada hook es una cicatriz. Cada cicatriz es una lección. Las lecciones se acumulan.2
Preguntas frecuentes
¿Puedo ver tus configuraciones de hooks?
Describo el sistema de hooks en mi comentario al NIST sobre seguridad de agentes y lo referencio a lo largo de la serie AI Engineering. Los hooks se registran en ~/.claude/settings.json y se despachan por tipo de evento a través de ~/.claude/hooks/dispatchers/.
¿Cómo afectan los hooks al rendimiento del agente?
Cada hook añade milisegundos por llamada a herramienta. Con 84 hooks, la sobrecarga total es de 200-400 ms por llamada a herramienta dependiendo de qué hooks se disparen. La sobrecarga es insignificante en comparación con el tiempo de inferencia del modelo (2-5 segundos por respuesta). Los hooks no son el cuello de botella.
¿Funcionan los hooks con otras herramientas de codificación con IA?
Los hooks son específicos de Claude Code (modelo de eventos PreToolUse, PostToolUse). El concepto se aplica a cualquier framework de agentes con soporte para middleware o plugins. Las implementaciones específicas no son portables, pero la taxonomía de cicatrices y la metodología reactiva se aplican universalmente.
¿Qué pasa cuando un hook bloquea una acción?
Los bloqueos duros (exit 2) previenen la acción e inyectan un mensaje explicando por qué. El agente ve la razón del bloqueo y se ajusta. Los hooks informativos (exit 0) registran la preocupación pero permiten la acción. Las operaciones destructivas usan bloqueos duros. La mayoría de las otras categorías usan hooks informativos. La barrera de contraseña solo se usa para las operaciones más peligrosas (borrado de caché, eliminación de infraestructura).
¿Cómo decides entre bloqueo duro e informativo?
Dos clases obtienen bloqueos duros: operaciones destructivas (borrados de caché, eliminaciones de bases de datos, force pushes, modificaciones de infraestructura) y exposición de credenciales (lectura de archivos secretos, acceso a almacenes de claves). Todo lo demás obtiene registro informativo. La distinción es la severidad de la consecuencia: si la acción se puede deshacer de forma barata y no filtra secretos, una advertencia es suficiente. Si la acción es irreversible o expone credenciales, un bloqueo duro es necesario.
Fuentes
-
Blake Crosley, “What I Told NIST About AI Agent Security,” blakecrosley.com, febrero de 2026. Tasa de verificación fantasma del 12 % en más de 60 sesiones autónomas. 84 hooks cubriendo 15 de los 26 tipos de eventos del ciclo de vida de Claude Code (v2.1.116), metodología de detección de desviaciones. ↩
-
Blake Crosley, “Compound Context: Why AI Projects Get Better the Longer You Stay With Them,” blakecrosley.com, marzo de 2026. Marco de composición del contexto: hooks como una de seis categorías que acumulan rendimientos. ↩
-
Blake Crosley, “The Supply Chain Is the Attack Surface,” blakecrosley.com, marzo de 2026. Brecha de composición: componentes individualmente autorizados produciendo resultados no autorizados. ↩
-
Blake Crosley, “Deploy and Defend: The Agent Trust Paradox,” blakecrosley.com, marzo de 2026. Incidente del borrado de caché y respuesta con la protección de API destructiva. ↩
-
Christoph Riedl et al., “Agents of Chaos,” arXiv:2602.20021, febrero de 2026. Estudio multiuniversitario de 14 días (Northeastern, Stanford, Harvard, MIT, CMU). Seis agentes de IA, 10 vulnerabilidades de seguridad identificadas incluyendo respuesta desproporcionada, secuestro de identidad y bucles infinitos. ↩