Correr dos agentes de IA en un mismo repositorio ya dejó de ser un escenario exótico. Dos terminales de Claude Code sobre el mismo proyecto. Cursor abierto mientras un agente de línea de comandos avanza con el backlog. Un agente programando de noche mientras duermes o, como me pasó hoy, mientras yo mismo estaba editando. Nadie te advierte del modo de fallo, porque no parece un problema de IA. Parece un problema de git, y lo es: las dos sesiones comparten un solo working tree. Los mismos archivos, el mismo index, el mismo HEAD. El primer agente que ejecuta git switch le mueve el piso al otro.
Hoy me pasó de verdad. Si no me costó nada fue porque un hook lo detectó antes de que el segundo agente tocara git. Este post es el incidente, la solución en dos partes (ambas nativas, sin dependencias nuevas) y dos detalles que me habrían mordido si no los hubiera vivido en carne propia.
Un working tree, un HEAD, dos escritores
Un checkout estándar de git te da exactamente un working tree. HEAD es un único puntero que dice “los commits van aquí”. Toda sesión que arranques dentro de ese directorio comparte todo eso: los archivos en disco, el staging area y ese único HEAD.
Ese compartir es invisible hasta que dos sesiones escriben. Entonces falla de tres maneras distintas:
- Commits en la rama equivocada. El agente A está en
feature-x. El agente B ejecutagit switch -c feature-y, trabaja un rato y sigue con lo suyo. El próximo commit del agente A cae en silencio sobrefeature-y, porque HEAD se movió y nadie le avisó. - Checkouts contra trabajo sin confirmar. Si el agente A tiene archivos sucios, el cambio de rama del agente B o se niega (el mejor caso) o arrastra los cambios de A a una rama donde no tienen sentido.
- Colisión simple de archivos. Los dos agentes editan el mismo archivo al mismo tiempo. No hace falta ningún comando de git; gana el último que escribe, en silencio.
Nada de esto es nuevo: dos humanos por SSH sobre un mismo checkout siempre pudieron hacérselo. Lo nuevo es que los agentes lo volvieron probable: son rápidos, no tienen supervisión y jamás miran la otra terminal antes de escribir.
El día que casi me pasa
El incidente exacto, porque la versión abstracta no transmite lo silencioso que es este fallo.
Una sesión de Claude Code trabajaba en vivo en mi repo heroweb.dev, en la rama en-full-parity, con el working tree sucio: cambios sin confirmar en HomePage.astro y es.json. Mientras tanto, un segundo agente arrancó en el mismo directorio para otra tarea.
Su primera acción de git habría sido un git switch a una rama nueva, o un commit. Cualquiera de las dos mueve HEAD debajo de la primera sesión: su siguiente commit cae en la rama equivocada o, peor, el switch aplasta el trabajo sin confirmar de HomePage.astro, que no existía en ningún otro lugar.
Lo que pasó en realidad: un hook de SessionStart imprimió una advertencia en el momento en que el segundo agente arrancó.
OTRA SESIÓN DE CLAUDE ESTÁ ACTIVA en este mismo directorio. Están compartiendo un working tree.
El segundo agente leyó eso y, en lugar de tocar git, se movió a un worktree aislado. Costo total: una línea de advertencia y un bun install. Sin el hook, el costo habría sido una tarde forense desenredando qué commits pertenecían a qué rama.
Dos agentes de IA en un mismo repositorio, sin choques: un worktree por agente
La mitad de aislamiento de la solución es git worktree, que existe en git desde la versión 2.5, allá por 2015. Un worktree es un segundo directorio de trabajo conectado al mismo repositorio: sus propios archivos en disco, su propio index y, la parte que importa aquí, su propio HEAD.
# Desde cualquier punto dentro del repo: crea un directorio hermano
# con su propia rama, cortada desde origin/main
git worktree add ../heroweb-autorun -b autorun/post-dos-agentes origin/main
# node_modules no se comparte: instala una vez por worktree
cd ../heroweb-autorun
bun installAhora el segundo agente vive en ../heroweb-autorun. Puede cambiar de rama, hacer commits y pushear sin mover nada en el checkout original. Los dos directorios comparten un solo almacén de objetos (los datos de .git), así que los commits hechos en cualquiera son visibles al instante en ambos, las ramas son comunes y un push desde el worktree es un push normal. No es un clone: no hay segunda copia del historial ni pasos de sincronización.
Mi acuerdo de trabajo: un worktree por agente y, adentro, una rama por unidad de trabajo. El agente hace commit, pushea, abre un PR, y el árbol sucio de la otra sesión nunca se entera. Es el mismo mecanismo que usa mi agente nocturno para que las unidades paralelas no se pisen. Lo de hoy fue la edición diurna de la misma lección.
Cuando el trabajo ya se integró, limpia con git worktree remove ../heroweb-autorun; git worktree prune barre los registros huérfanos.
La otra mitad: agentes que saben que no están solos
Los worktrees frenan la colisión, pero fíjate en lo que no hacen: no le avisan a nadie que la colisión venía en camino. Mi segundo agente solo buscó un worktree porque algo le dijo que había otra sesión activa.
Ese algo es una capa ligera de contexto compartido. La mía tiene dos piezas:
- Un puntero de sesión: cada sesión registra dónde está trabajando (directorio, rama, timestamp) en un lugar común, para que haya una respuesta barata y grepeable a “¿quién está activo ahora mismo?”.
- Un hook de SessionStart que al arrancar revisa los registros de sesiones recientes e imprime la advertencia que viste arriba cuando otra sesión viva reclama el mismo directorio.
Eso es coordinación, no aislamiento, y necesitas ambas. Aislamiento sin coordinación obliga a cada agente a crear worktrees a la defensiva: derrochador, pero sobrevivible. Coordinación sin aislamiento deja a los agentes conociéndose y aun así compartiendo un HEAD: educado, pero condenado. La mitad de coordinación es la misma capa de memoria compartida que ya uso para conservar contexto entre sesiones de Claude Code; “¿quién más está vivo en este directorio?” es solo una pregunta más que esa capa responde.
El principio de fondo: un agente debe enterarse de que no está solo por una advertencia al arrancar, no por un commit corrupto al final.
Dos detalles que te van a encontrar
Los dos me mordieron mientras armaba la detección, y los dos aplican mucho más allá de mi setup.
Ancla la detección al directorio de arranque, no a la raíz de git. Mis proyectos viven en una carpeta madre que contiene varios repos, y suelo lanzar las herramientas desde esa carpeta, que no es un repo de git. Ahí, git rev-parse --show-toplevel no devuelve nada. Cualquier chequeo de concurrencia anclado a “la raíz de git” se apaga en silencio justo en la situación para la que existe. Ánclalo al directorio de invocación: ese siempre existe.
Exclúyete a ti mismo. Un chequeo ingenuo de “¿hay otro agente activo en este directorio?” se encuentra a sí mismo. Y a sus propios subagentes, si lanza ayudantes que también registran sesiones. El chequeo tiene que filtrar el ID de la sesión actual y todo lo que esa sesión haya lanzado, o cada arranque produce una falsa alarma, aprendes a ignorar la advertencia, y una advertencia ignorada es peor que ninguna.
El costo honesto
Los worktrees no son gratis. node_modules no se comparte entre ellos, así que cada worktree necesita su propio bun install (o npm, o pnpm, lo que uses), lo que cuesta un minuto y algo de disco. Si tu proyecto arrastra estado local pesado, una base de datos de desarrollo o fixtures grandes, eso también necesita su propia historia por worktree.
Esa es toda la factura. Compárala con la alternativa, un commit en la rama equivocada o trabajo sin confirmar aplastado en un árbol que otro agente estaba usando, y es el seguro más barato que vas a comprar.
Aburrido a propósito
Aquí no hay nada que instalar ni ningún producto que comprar. Git resolvió el problema del aislamiento hace una década; un archivo puntero y un hook de arranque resuelven el problema de la conciencia mutua. Si vas a correr más de un agente contra un repo, y lo vas a hacer, conecta las dos mitades antes del día en que un agente mueva HEAD debajo del otro. Hoy me tocó la versión con advertencia de esa lección. La otra versión cuesta una tarde entera.