Bun reescribió un millón de líneas de Zig a Rust en once días | --no-rollback

Bun reescribió un millón de líneas de Zig a Rust en once días

En un momento en el que todavía nadie tiene claro cómo revisar tanto código generado por IA, la respuesta de Bun —automatizar también la revisión— choca de frente con la de Zig, que meses antes había prohibido exactamente ese tipo de contribuciones por considerarlas "basura invariable".
Rubén S.
Auditor que audita al que audita al auditor
12 min

Bun —el runtime de JavaScript que sustituye a Node, ahora propiedad de Anthropic— compila y distribuye su binario para seis combinaciones de sistema operativo y arquitectura: Linux y macOS en x64 y arm64, Windows en x64 y arm64. El 3 de mayo, ese código se escribía todavía en Zig. El 9, ya reescrito en Rust, conseguía compilar sin errores y pasar toda la batería de pruebas en la primera de esas seis plataformas, Linux. El 14, a las 12:23 de la madrugada, lo mismo ocurría a la vez en las seis. Entre medias: 6.502 commits, más de un millón de líneas añadidas al pull request final, y ni una sola línea de ese millón escrita por una persona. Bun acababa de reescribirse a sí mismo de Zig a Rust en once días, con 64 instancias de Claude Code trabajando en paralelo y un solo ingeniero humano supervisando el proceso.

Jarred Sumner, su creador, publicó el relato completo el 8 de julio bajo el título “Rewriting Bun in Rust”. Es un post largo, técnico y, para cualquiera que trabaje con agentes de código, genuinamente interesante: describe con detalle inusual cómo se organiza un ejército de modelos para no producir un desastre. Seis días después, Andrew Kelley —creador de Zig— publicó su respuesta. El titular que se quedó fue el de The Register: “unreviewed slop”. Basura sin revisar.

Lo que hace interesante esta historia no es quién tiene razón. Es que ambos post explican, desde ángulos opuestos, la misma pregunta que este año se ha vuelto imposible de evitar: si un agente puede escribir un millón de líneas en una semana, ¿qué significa exactamente “revisar” ese código?

Un runtime que llevaba años peleándose con su propia memoria

La versión oficial del porqué es, en lo técnico, razonable. Zig da control manual total sobre la memoria y no tiene constructores ni destructores implícitos: cada free depende de que alguien recuerde escribir el defer correspondiente en cada sitio de llamada. Bun, además, mezclaba ese modelo con el recolector de basura de JavaScript, una combinación que Sumner describe con precisión quirúrgica: “mixing GC with manually-managed memory is an uncommon enough thing for software to need that no language really designs for it”. El resultado, con los años, fue una lista recurrente de crashes con nombre y apellido: use-after-free en node:zlib, otro en node:http2, un double-free en el parser de CSS.

Rust no elimina esa clase de errores por talento del compilador. Los convierte en errores de compilación. Ese fue el argumento central: cambiar de lenguaje para que el borrow checker hiciera, de forma mecánica y obligatoria, lo que en Zig dependía de que un humano —o, cada vez más, un agente— se acordara de hacer bien cada vez.

La reescritura no fue una reescritura, fue una cadena de montaje

Lo que separa este proyecto de un “le pedí a Claude que tradujera mi código” es la cantidad de estructura que Sumner construyó antes de escribir la primera línea de Rust. Las primeras tres horas se fueron en documentación de proceso, no de producto: un PORTING.md con el mapeo de patrones Zig a Rust, y un LIFETIMES.tsv con el análisis de lifetimes de cada campo de cada struct del codebase, revisado dos veces de forma adversarial antes de dejar que ningún agente tocara código real.

Con las guías ya probadas contra sí mismas, el equipo hizo un primer ensayo con tres archivos, usando la misma arquitectura de revisión que después se replicaría a escala completa (la detallo en el siguiente apartado). Solo cuando ese ciclo funcionó en pequeño se lanzó la producción real: los 1.448 archivos .zig convertidos a .rs simultáneamente, repartidos en 4 worktrees de 16 Claudes cada uno, 64 agentes trabajando a la vez. Los errores del compilador —unos 16.000 en el pico— se agruparon por crate y se trataron literalmente como una cola de trabajo que había que vaciar. En el momento de mayor velocidad, el sistema producía 695 commits por hora; en algún minuto concreto, 58.

Es una cifra que vale la pena detener un segundo: 58 commits en sesenta segundos es casi uno por segundo, sostenido, generado por un proceso que ningún humano estaba leyendo línea a línea mientras ocurría. La pregunta no es si eso es posible —evidentemente lo es, el diff se fusionó y compila en seis plataformas—. La pregunta es qué sustituyó, en ese minuto, a la lectura humana.

El revisor también era Claude

La respuesta de Bun a esa pregunta tiene un nombre: revisión adversarial. La arquitectura que usaron para cada cambio real —no solo el ensayo de tres archivos— separaba explícitamente los roles: un Claude implementador que escribía el puerto, dos Claudes revisores independientes que solo recibían el diff, sin el contexto de por qué se había escrito así, y buscaban activamente motivos para rechazarlo. “The implementer doesn’t review. The reviewer doesn’t implement”, resume Sumner. El ejemplo que el propio Bun eligió para ilustrar el mecanismo es revelador: uno de los revisores encontró un Box<Pipe> que se liberaba mientras libuv todavía mantenía un puntero activo a él, exactamente la familia de use-after-free que había motivado abandonar Zig en primer lugar. La reescritura tenía que demostrar, desde dentro y con sus propias herramientas, que podía cazar la misma enfermedad que la había originado.

Son bugs de los buenos: el tipo de error que un compilador no marca, que un test superficial no cubre, y que solo aparece cuando alguien —o algo— lee el código preguntándose activamente “¿cómo rompo esto?”. Hasta aquí, el diseño es sólido, y probablemente sea la parte de este post que más vale la pena robar para cualquier equipo que use agentes en serio: no le pidas al mismo proceso que escriba y que se autoevalúe.

Pero conviene no cerrar la idea ahí, porque el propio diseño reintroduce, un nivel más arriba, el problema que dice resolver. Los “adversarios” que revisan no son independientes en el sentido en que lo sería un ingeniero humano con memoria, incentivos propios y la capacidad de negarse a aprobar algo por razones que no puede articular del todo. Son el mismo modelo, con el mismo entrenamiento, ejecutando el mismo tipo de heurística de búsqueda de errores que ya sabemos que tiene puntos ciegos sistemáticos. Ya lo señalamos al hablar de qué tan poco confiable resulta que una empresa evalúe su propia seguridad: separar roles dentro de un mismo sistema reduce un tipo de sesgo, pero no sustituye a que alguien fuera del sistema pueda decir que no.

Verificar once días de IA con lo que ya existía

Donde el argumento de Bun se vuelve más difícil de descartar es en el aparato de verificación que rodeaba todo esto, porque no se construyó para la ocasión: ya existía, y la reescritura tuvo que sobrevivirlo entero, sin recortar nada. El test suite —independiente del lenguaje del runtime, escrito en TypeScript— tenía cerca de un millón de assertions, y ni un solo test se omitió o se eliminó durante el proceso. A eso se sumó fuzzing guiado por cobertura corriendo 24 horas al día contra los parsers, con más de cien mil millones de ejecuciones acumuladas y unos 15 pull requests de correcciones solo de esa fuente; Address Sanitizer en cada commit; integración continua en seis plataformas con hasta 60 shards cada una; y, después de fusionar, once rondas adicionales de revisión de seguridad.

El resultado fueron 19 regresiones detectadas y corregidas, casi todas del tipo que solo aparece al cruzar la frontera entre dos lenguajes con semánticas distintas. La más reveladora: los binarios de release de Rust conservan comprobaciones de límites que el ReleaseFast de Zig elimina por defecto, lo que en un punto concreto invirtió el problema que la reescritura pretendía resolver —abrió un acceso fuera de rango que antes no existía—. Ninguna de las 19 es un fallo de lógica de negocio: todas nacen de asumir que traducir sintaxis entre lenguajes basta para conservar el comportamiento, cuando lo que de verdad cambia, de un lenguaje a otro, es el contrato implícito que cada uno asume con quien lo escribe.

Los números finales, ya en v1.4.0, son consistentes con ese esfuerzo: el consumo de memoria en una prueba de 2.000 iteraciones de Bun.build() bajó de 6.745 MB a 609 MB, el binario se redujo en torno a un 20% según plataforma, y el throughput HTTP mejoró entre un 2,8% y un 4,8%. Bun también reconoce, sin esconderlo, que el puerto deja unos 13.000 bloques unsafe en el codebase —el 78% de ellos, llamadas FFI de una sola línea a C o C++—, una cifra que analistas externos como Fawad Hussain Syed han señalado como la deuda de seguridad real que queda pendiente de una migración hecha a esta velocidad, y que el propio equipo dice estar reduciendo de forma incremental de cara a v1.4.

El proyecto que Bun dejó ya había prohibido esto

Aquí es donde el relato oficial de Bun deja fuera una fecha que lo cambia todo. La Zig Software Foundation no prohibió las contribuciones de código generado por IA a su repositorio después de esta reescritura, como reacción defensiva. Lo hizo antes, y Bun ya lo sabía: a finales de abril, documentando su propio fork del compilador de Zig, el equipo de Bun escribió sin rodeos que no pensaban proponer esos cambios río arriba “as Zig has a strict ban on LLM-authored contributions”. Semanas antes de que existiera un solo commit de la reescritura a Rust, Bun ya desarrollaba fuera del proyecto del que dependía, precisamente por el choque entre su forma de trabajar y la política de contribuciones de ese proyecto.

La política en sí, Andrew Kelley la había explicado meses antes en un podcast de JetBrains, con una franqueza que no necesitaba matices: “People are sending us contributions that have no value whatsoever. They have negative value, because they take review time away from the team.” Con 200 pull requests abiertas y un equipo de revisores que no escala, cualquier contribución generada por IA —calificada sin margen de duda como “invariably garbage”— no era un problema de calidad promedio. Era un impuesto sobre el tiempo del único recurso que no se puede clonar 64 veces: la atención de un revisor humano que entiende de verdad lo que está leyendo.

El 14 de julio, seis días después del post de Sumner, Kelley publicó “My Thoughts on the Bun Rust Rewrite”. Es un texto largo, desordenado a propósito y con un pie en cada lado: técnico en una mitad, personal y amargo en la otra. La parte técnica pregunta lo obvio y lo hace con precisión incómoda: si Bun ya alegaba tener un test suite robusto y aun así acumulaba los bugs de memoria que motivaron la reescritura, ¿qué garantiza que ese mismo test suite vaya a cazar los bugs de “a million lines of unreviewed slop”? Describe años de ver “hacks on top of hacks” en el código de Bun y un patrón de “recklessly speeding past feature after feature with very little time taken for reflection” que, según Kelley, no lo causó Zig ni lo cura Rust.

La parte personal es la que se llevó los titulares, y con razón: documenta que la donación anual de 60.000 dólares de Bun a la Zig Software Foundation cesó en silencio, que Bun dejó de asistir a reuniones programadas sin cancelarlas, y describe con una frase que ya circula como cita —“the grapevine was large and healthy and full of juicy grapes, and all those grapes contained the juice of the same message: Jarred was a stinky manager”— el deterioro de una relación que llevaba años agriándose antes de que hubiera una sola línea de Rust de por medio. La propia comunidad de Zig se dividió al leerlo: en el hilo de Ziggit, unos agradecen la honestidad de un texto tan poco filtrado para venir de quien lidera un lenguaje de programación; otros señalan, con razón, que la mitad personal resta fuerza a la mitad técnica, que era la que de verdad merecía respuesta.

Nadie de Bun ha respondido, y puede que esa sea la respuesta

Aquí hay que ser preciso con lo que no existe: a fecha de hoy, no hay ninguna respuesta pública de Jarred Sumner, de Bun o de Anthropic al post de Kelley. Ni un hilo, ni una entrada de blog, ni una entrevista donde alguien del otro lado conteste directamente a “unreviewed slop”. Es un silencio llamativo viniendo de una empresa que acaba de publicar 3.000 palabras muy detalladas presumiendo, precisamente, de su proceso de revisión.

Se puede imaginar la respuesta que Bun escribiría si quisiera, porque ya está casi toda en su propio post de julio: los números de fuzzing, las 19 regresiones cazadas antes de llegar a producción, las once rondas de revisión de seguridad. Un argumento defendible sería que la revisión no desapareció, se automatizó y se volvió más exhaustiva de lo que un equipo de tres o cuatro personas podría sostener a mano. Pero esa respuesta no ha llegado, y la ausencia dice algo que la reescritura, sin querer, ya había demostrado: el objetivo explícito de todo el proceso era dejar de depender de que un humano con tiempo limitado leyera cada línea. Contestarle a Kelley línea por línea sería, precisamente, volver al modelo que la reescritura entera existió para evitar.

Reescribir no es lo mismo que revisar

La disputa se ha contado como una pelea de lenguajes —Zig contra Rust, memoria manual contra borrow checker— y en el fondo no lo es. Los dos bandos están de acuerdo en lo importante: que un revisor humano, solo, ya no puede leer todo el código que un equipo produce hoy. Donde discrepan es en qué pasa después de aceptar eso. Zig responde bajando el volumen: menos contribuciones, cada una revisada por una persona que entiende el proyecto, aunque eso signifique 200 pull requests esperando en cola. Bun responde subiendo la velocidad de la máquina que sustituye a esa persona: más agentes, más capas de verificación automática, y la apuesta de que un aparato de pruebas suficientemente grande puede hacer el trabajo que antes hacía un revisor con criterio.

Ninguna de las dos apuestas está resuelta todavía. La de Zig se paga en velocidad de desarrollo y en una cola de trabajo que crece. La de Bun se paga, por ahora, en 13.000 bloques unsafe sin auditar del todo, en un post de seis mil palabras que nadie del otro lado ha respondido, y en la pregunta que Kelley dejó sin cerrar y que ningún test suite, por grande que sea, puede responder por sí solo: quién decide, al final, si un millón de líneas que nadie leyó entero está realmente bien.