Article

Quién dice el commit que lo escribió

Una política de atribución de IA es una frase en una guía de contribución hasta que algo lee el campo de autor. Teníamos la política, teníamos la verificación, y un commit escrito por un bot se fusionó limpio de todos modos. Lo que encontramos al corregirlo.
August 2, 2026
Topics:AI Agent GovernanceCI/CDDeveloper Experience
Tags:GitHub Actions

La mayoría de las organizaciones que usan agentes de codificación han escrito una regla sobre la atribución. La nuestra es una frase: el trabajo se lee como escrito por el operador humano. No es un secreto: las herramientas se nombran abiertamente en nuestros repositorios. La regla trata sobre el crédito.

La aplicábamos con una verificación de CI que escanea los mensajes de commit en busca de trailers de coautoría y pies de "generado con". Funcionaba. Llevaba semanas ejecutándose en trece repositorios.

Entonces se fusionó una solicitud de extracción con un agente nombrado en el campo de autor de uno de sus commits. La verificación se ejecutó en esa rama. Reportó que había eliminado trailers. Aprobó.

Tenía razón en aprobar, según sus propias reglas. Ese es el problema.

El trailer no es la afirmación

Un trailer de coautoría es una forma de dar crédito a una IA. El campo de autor es la otra, y es la más visible. git log muestra el autor. La lista de commits en una plataforma de alojamiento muestra el autor. Ninguna muestra los trailers a menos que uno vaya a buscarlos.

Así que la verificación leía la mitad más silenciosa de la superficie y reportaba sobre el total. Un agente que es autor de un commit no agrega un trailer acreditándose a sí mismo: simplemente hace el commit, y su identidad va en el campo reservado exactamente para esa afirmación.

La corrección no es complicada: leer el nombre y el correo del autor y del committer junto con el mensaje. Lo interesante fue todo lo que salió mal en el camino.

Cuatro formas de rechazar accidentalmente a un humano

Buscar nombres de proveedores en el mensaje de un commit es seguro, porque la verificación solo se activa dentro de una construcción de atribución: un trailer, un pie. Un nombre de proveedor en prosa es solo una palabra.

Un campo de identidad no tiene construcción. El campo es la afirmación. Así que la coincidencia debe ser más estricta, y cada forma en que la estrechamos al principio fue incorrecta:

  • Coincidencia por subcadena. El nombre de un proveedor está dentro del apellido Guillamas. Otro está dentro de Codexis. Un trailer que acredite a "Guillamas" no es plausible; un campo de autor que lo diga es completamente plausible.
  • Límites de palabra. La corrección obvia es un límite de palabra. En la mayoría de las variantes de regex eso se construye sobre la clase de caracteres de palabra, que incluye el guion bajo, de modo que el patrón dejó de coincidir con copilot_swe_agent, que es la forma más común que adopta realmente el identificador de un bot. El límite agregado para proteger apellidos omitía exactamente las identidades que buscábamos.
  • Nombres de proveedores que son nombres de personas. Claude y Devin son nombres que tienen las personas. Gemini, Cursor y Codex son palabras corrientes. Una regla que los marque a simple vista rechaza a un colaborador por llamarse de forma incorrecta.
  • Palabras marcadoras. Calificamos los nombres ambiguos exigiendo un marcador de bot o agente junto a ellos, y luego dejamos agent sin delimitar, de modo que coincidía dentro de agentur, la palabra alemana para agencia, y dentro del apellido Reagent.

Cuatro intentos, cuatro formas distintas de rechazar una contribución externa. La forma final divide el vocabulario: los nombres inequívocos de productos y empresas coinciden por sí solos; los ambiguos necesitan un marcador de bot o agente junto a ellos, y cada marcador se delimita de la misma manera.

La regla que deliberadamente no escribimos

Nuestras órdenes permanentes también exigen que los commits lleven una dirección de la empresa. Habría sido trivial agregarla aquí, y habría sido un error.

Esta verificación se ejecuta en nuestros proyectos publicados. En ellos, un commit desde una dirección que no nos pertenece es una contribución externa, aquello para lo que existe un proyecto de código abierto. Una regla que hiciera fallar cada solicitud de extracción externa se apagaría en una semana, y se llevaría consigo la mitad relativa a la autoría.

Así que la regla del dominio se queda donde corresponde: una orden permanente que sigue un mantenedor, no una compuerta que rechaza a desconocidos. Escribimos el razonamiento dentro de la propia función, porque de lo contrario la omisión parece algo que nadie llegó a hacer.

La misma lógica decidió que los bots automatizados de dependencias y de publicación de versiones sigan aprobando. No todo bot es una afirmación de autoría. La regla trata sobre acreditar a la IA con la autoría, no sobre si un humano pulsó el botón.

Dos elusiones que solo existían juntas

La primera versión de la verificación de identidad dividía su entrada por un carácter de control, con un comentario que explicaba que solo el mensaje podía contener ese byte.

El nombre de un autor lo escribe quien hace el commit. En una solicitud de extracción desde una bifurcación, eso es elección de un tercero. Un nombre que contuviera el separador producía un número incorrecto de campos, y el analizador, al encontrar un registro que no podía dividir, lo omitía y seguía reportando sobre el resto.

Ninguna de las dos decisiones es obviamente incorrecta por sí sola. Un separador personalizado es corriente. Omitir un registro malformado tiene aspecto defensivo. Juntas sí lo son: elabore un nombre, eluda el escaneo, y la ejecución sigue volviendo limpia.

Ambas están corregidas. El flujo está delimitado por NUL, que es el único byte que el sistema de control de versiones garantiza que no puede aparecer en esos campos, y una salida no analizable ahora lanza un error en lugar de saltarse. Una compuerta que escanea discretamente menos commits de los que se le pidió es indistinguible de una que no encontró nada.

Las pruebas fueron la parte que casi nos engaña

La verificación no tenía ninguna prueba. Había sido revisada, y revisar no es lo mismo que ejercitar.

Escribimos pruebas y luego les aplicamos pruebas de mutación: revertir cada corrección por turno y confirmar que la suite pasa a rojo. Dos veces, no lo hizo.

El primer fallo fue una prueba de escape de anotaciones que aprobaba con el escape eliminado. Suponía que un atacante podía poner un salto de línea en el asunto de un commit. El sistema de control de versiones no lo permite, así que la prueba verificaba un caso que no puede ocurrir. El vector real era el signo de porcentaje: la plataforma de CI decodifica los escapes de porcentaje en el texto de las anotaciones, de modo que un asunto que contenga los caracteres que codifican un salto de línea se convierte en un salto de línea genuino, y lo que sigue inicia una nueva línea de salida del flujo de trabajo.

El segundo fallo fue una prueba para un nuevo manejador de errores que aprobaba con el manejador eliminado, porque la entrada que usaba activaba un manejador distinto y preexistente más arriba.

Ambas pruebas parecían correctas. Ambas habrían sido publicadas como evidencia de correcciones que nunca tocaron. Ninguna se habría detectado leyéndolas de nuevo con más cuidado: lo único que las detectó fue romper el código a propósito y observar qué seguía en verde.

Lo que le diríamos a alguien que empieza con esto

Tres cosas, en el orden en que nos afectaron.

Enumere las superficies antes de escribir el patrón. El crédito puede aterrizar en un mensaje de commit, un campo de autor, un campo de committer, el título de una solicitud de extracción o el cuerpo de una solicitud de extracción. Un control que cubre cuatro de cinco no es el ochenta por ciento de un control; es un control con un agujero, y el agujero está donde la herramienta escribe de forma natural.

Asuma que los campos son hostiles. Los campos de identidad los suministra quien hace el commit. En un repositorio público eso es cualquiera. Analizarlos con un delimitador que tienen permitido contener es la misma clase de error que confiar en el campo de un formulario.

Rompa sus propias pruebas antes de confiar en ellas. Una prueba que aprueba no es evidencia de nada hasta que la ha visto fallar por la razón correcta. Esto es más cierto, no menos, para los controles de seguridad: el modo de falla de una mala prueba de seguridad es el silencio, y el silencio se lee exactamente igual que la seguridad.

Por qué dejamos esto por escrito

Construimos herramientas de gobernanza de IA, así que vale la pena ser claros sobre lo que ocurrió aquí: teníamos la política, teníamos la verificación, la teníamos desplegada en cada repositorio, y un commit escrito por un bot se fusionó limpio de todos modos.

Eso no es un argumento contra tener el control. Es un argumento sobre qué es un control. Una política en una guía de contribución es una declaración de intención. Una política con una verificación delante es una declaración de intención más la suposición de que la verificación comprueba lo que la política dice. La brecha entre ambas es donde viven las fallas interesantes, y no se cierra porque usted haya escrito la política con cuidado.

La distancia entre "tenemos una regla sobre esto" y "algo se daría cuenta" suele ser mayor de lo que parece desde dentro. Vale la pena medirla en su propio entorno tecnológico antes de describírsela a nadie.