Agentes en paralelo en Windows: aislamiento y limpieza

“`html

En pocas palabras: Lanzar varios Claude Code en paralelo con git worktrees es lo fácil; la parte aburrida en Windows es aislar USERPROFILE y APPDATA por proceso, resolver la propiedad de archivos entre instancias y limpiar tras un fallo, porque HOME no aisla nada ahí.

El desarrollador detrás de NestMux publicó en dev.to una pieza técnica sobre ejecutar agentes en paralelo en Windows con una conclusión incómoda: el truco de lanzamiento es la parte fácil, y lo que decide si tu setup sigue vivo en un mes es el aislamiento, la propiedad de archivos y la limpieza después de un fallo.

Ejecutar agentes de código en paralelo en Windows consiste en correr varias instancias de asistentes como Claude Code sobre un mismo repositorio, cada una dentro de su propio git worktree. Un worktree es una función de git que permite tener varias ramas con checkout activo en directorios distintos del mismo repo al mismo tiempo, y es la base de casi todos estos setups. Según el artículo de NestMux, el aislamiento real exige separar USERPROFILE, APPDATA y el directorio de trabajo de cada proceso, porque la variable HOME no alcanza y los errores de borrado se disfrazan de problemas de permisos.

En 30 segundos

  • HOME no aisla nada en Windows: es una convención POSIX que muchas herramientas ignoran; necesitás USERPROFILE, APPDATA y un directorio de trabajo propio por proceso.
  • “Permission denied” casi nunca es permisos: es un proceso vivo que tiene ese directorio como cwd, y git reporta la negativa como error de permisos.
  • Matar el shell no alcanza: el que retiene el handle suele ser un hijo (node.exe) que heredó el directorio; hay que matar el árbol de procesos completo.
  • Tras un fallo, el orden importa: matar procesos, borrar el directorio a mano y recién después git worktree prune.
  • Los hardlinks no se detectan con lstat().isSymbolicLink(): hay que comparar file IDs, y aun eso falla en algunas configuraciones de Windows.

¿Por qué una sola variable de entorno no aisla agentes en Windows?

Porque HOME es una convención POSIX que algunas herramientas honran y Windows ni mira. Dos agentes corriendo bajo la misma cuenta de Windows comparten configuración y, lo más grave, la misma sesión autenticada. Si querés dos cuentas de Claude lado a lado, necesitás directorios home separados de verdad.

Ponele que levantás dos instancias para trabajar dos features a la vez. Seteás HOME distinto para cada una, arrancás la primera, funciona bárbaro, levantás la segunda, empiezan a compartir la identidad de git, un commit sale firmado con el nombre del otro agente, y te enterás media hora después cuando ya pusheaste al remoto. El autor lo describe como un agente “medio redirigido”: el CLI escribe su config en la ubicación nueva mientras git sigue escribiendo en la vieja.

Lo que necesitás por proceso es USERPROFILE y APPDATA apuntando cada uno a su propio subdirectorio, encima del directorio de trabajo. Nada más, nada menos. “Los agentes en paralelo solo son prácticos cuando los límites del workspace son aburridos y explícitos”, escribió Alex Shev en un comentario que terminó siendo mejor índice que el post original, y el autor lo reconoció sin vueltas.

¿Cómo se ejecutan agentes en paralelo en Windows sin que se pisen?

Cada agente necesita tres cosas propias: USERPROFILE, APPDATA y el directorio de trabajo actual fijado al momento del spawn. Con eso, más un worktree de git por agente, los límites quedan explícitos y el riesgo de que dos instancias editen los mismos archivos baja a casi cero. En nuestra guía completa sobre Claude profundizamos sobre esto.

Ojo con esto si construís tu propio launcher: el process.env de Node no siempre refleja variables inyectadas al hacer spawn en Windows. Si tu app lee process.env para decidir dónde guarda su storage y además redirige variables para procesos hijos, esas dos lecturas van a discrepar. La solución del equipo de NestMux fue una variable de entorno dedicada para storage y una función separada para “dónde debe arrancar un shell nuevo”, porque colapsarlas hacía que cada terminal nueva abriera dentro del directorio de configuración del agente (sí, en serio).

Ahora bien, el aislamiento total que nadie quiere. Tu CLAUDE.md y tus skills deberían ser iguales en todos los paneles, así que se enlazan a la copia global en vez de duplicarse. Y acá aparece el tema de los enlaces en Windows, que tiene más trampa de la que parece.

¿Por qué “Permission denied” no significa lo que creés?

Porque Windows no permite borrar un directorio que es el directorio actual de algún proceso vivo, y git reporta esa negativa como error de permisos. Los permisos no tienen nada que ver. Armá un repo con un worktree, meté un proceso adentro con un directorio de trabajo real, y al intentar eliminarlo vas a ver: error: failed to delete 'C:/dev/feat': Permission denied.

En Linux el mismo borrado funciona sin drama, y por eso estos bugs llegan primero como reportes de usuarios de Windows que nadie puede reproducir en su máquina. El mensaje de error, dicho sea de paso, apunta activamente en la dirección equivocada.

SituaciónLinuxWindows
Borrar un directorio que es el cwd de un procesoEl borrado funcionaFalla con “Permission denied”
HOME como variable de aislamientoConvención que las herramientas respetanIgnorada a medias: hacen falta USERPROFILE y APPDATA
Symlink de archivoSe crea sin privilegiosRequiere elevación o Modo Desarrollador
ejecutar agentes en paralelo en windows diagrama explicativo

Dos descubrimientos que el autor confiesa haber hecho recién al escribir un teardown confiable. Primero: Set-Location de PowerShell no reproduce el problema, porque es un concepto de provider montado encima del proceso y el working directory real queda donde arrancó. Un Start-Process powershell -Command "Set-Location C:\dev\feat; ..." va a dejar que el borrado pase, y vas a concluir que el bug no existe (spoiler: existe). Segundo: matar el shell no alcanza. En su prueba, mató el shell y el borrado igual falló; el que retenía el handle era node.exe, un hijo que heredó el directorio y sobrevivió a su padre. Necesitás el árbol de procesos, no el proceso. Ya lo cubrimos antes en elegir entre Sonnet y Opus.

La consecuencia práctica: registrá el cwd de cada panel al momento del spawn y matá por prefijo de ruta normalizado, porque en Windows el mismo worktree aparece como C:\dev\feat o C:/dev/feat según quién escribió la ruta.

¿En qué orden se limpia después de un agente fallido?

En este orden: matar el árbol de procesos que retiene el handle, borrar el directorio a mano y recién después correr git worktree prune. Cualquier otro orden deja estado huérfano que ni git ni tu app saben cómo tratar.

Acá viene lo contraintuitivo: un git worktree remove fallido no es un no-op. Git borra el contenido, borra el directorio administrativo bajo .git/worktrees, saca la entrada de su lista, y recién ahí choca contra el directorio raíz bloqueado y frena. Lo que sobrevive es una carpeta vacía que git no reconoce, no puede borrar y no considera dangling. La rama sigue ahí. Y git worktree prune no tiene nada que podar, porque la metadata ya voló.

  • Matá al que retiene el handle, árbol de procesos incluido, no solo el shell padre.
  • Borrá el directorio vos mismo, porque git ya se dio por vencido a medias.
  • Recién después corrés git worktree prune, para cubrir el caso en que git no llegó a limpiar su propia metadata.

Podar primero es peor: podés estar podando metadata que todavía referencia el directorio que estás por borrar. La regla que el equipo de NestMux adoptó, y que les costó un bug aprender, es que si cualquiera de esos pasos falla, hay que conservar la entrada y reportar el fallo. La versión tentadora es dropear tu propio registro y darlo por eliminado, porque el usuario pidió que desapareciera. Entonces el próximo refresh lee git worktree list o la carpeta leftover, el worktree reaparece viéndose sano, y ya no se puede borrar desde la UI porque el código de borrado asume el estado que acaba de perder. Un borrado parcial reportado como éxito es peor que un mensaje de error.

Dos extras del mismo territorio: reconciliá al leer, comparando contra git worktree list en cada listado porque la gente borra worktrees por fuera de tu app; y jamás crees un worktree dentro de .git, porque git lo crea y después se niega a tratarlo como working tree, y el único camino de salida es el borrado manual. Más contexto en integrar Claude en tus automatizaciones.

¿Symlink, junction o hardlink: cuál conviene para compartir configuración?

Para directorios, junctions, que no requieren elevación. Para archivos, el symlink necesita elevación o Modo Desarrollador en Windows, así que el fallback es el hardlink. Y el hardlink tiene un diente venenoso que vale la pena conocer antes de que te muerda.

Tipo de enlace¿Requiere elevación o Modo Desarrollador?Sirve paraTrampa
Symlink de archivoArchivos sueltos de configHay que reemplazarlo por copia real al desvincular
Junction de directorioNoDirectorios como skillsSolo directorios, no archivos
Hardlink de archivoNoFallback cuando no hay elevaciónlstat().isSymbolicLink() devuelve false; detectarlo exige comparar file IDs

El escenario concreto: ofrecés un botón de “desvincular esta cuenta de la config compartida” y lo implementás como “reemplazar symlinks por copias reales”. Los archivos hardlinkeados se saltan, la cuenta sigue editando tu config global en silencio, y la UI dice que está desvinculada. Detectarlos exige comparar el file ID del archivo (nFileIndexHigh y nFileIndexLow). Salvedad: en algunas configuraciones de Windows esa comparación hace que cualquier par de archivos parezca idéntico, así que probalo en tu máquina antes de confiar.

¿Por qué los logs por panel no alcanzan con cuatro agentes?

Porque un transcript por proceso es fácil y un timeline unificado con timestamps, exit codes y atribución por worktree es difícil, y justo eso es lo que necesitás cuando algo falla de madrugada. Cuatro agentes trabajando, el run muere de noche, y la pregunta es quién tocó qué y en qué orden. Con transcripts por panel te toca reconstruirlo a mano desde cuatro scrollbacks.

El estado actual de NestMux, según el post: un transcript por panel exportable a markdown y un log de setup por worktree con tope de 200 líneas y secretos redactados. Lo que no existe es cualquier cosa unificada. El autor lo declara como gap abierto, sin fecha de envío, y es de lo más honesto del texto: los transcripts por panel eran fáciles, y un log cross-pane con atribución correcta no lo es, sobre todo cuando los paneles aparecen y desaparecen.

¿Qué ajustes necesita el setup que corre antes del agente?

Tres, ninguno clever: timeout por comando, cancelación en teardown y redacción de secretos. La mayoría de los setups paralelos corren algo después de crear el worktree, un copy o un build, y ese es el lugar donde los fallos se tragan solos.

  • Timeout por comando. El comando que falla lo ves. El que te cuesta es el que no imprime nada y nunca termina; esperar un prompt en stdin es el caso clásico. Diez minutos por comando y después kill.
  • Cancelación en teardown. Si el setup sigue corriendo cuando alguien elimina el worktree, cancelalo primero. Si no, estás borrando un directorio que un proceso vivo está escribiendo, y volvés a la sección de handles.
  • Redacción de secretos. Las líneas TOKEN=… de un dump de env o de un install verboso se stripean al entrar, no al salir, por si los logs se persisten.

Qué está confirmado y qué sigue pendiente

Todo lo anterior sale del post técnico publicado en dev.to, así que conviene separar lo verificado de lo declarado como deuda. Para más detalles técnicos, mirá la API de Claude 3 Opus.

  • Confirmado: HOME es insuficiente y hacen falta USERPROFILE, APPDATA y CWD propios; las junctions no requieren elevación; el “Permission denied” es un lock de cwd, no de permisos; matar el shell padre no libera el directorio; el orden de recuperación es procesos, borrado manual y prune al final.
  • Existente hoy en NestMux: transcript por panel exportable a markdown y log de setup por worktree con tope de 200 líneas y secretos redactados.
  • Pendiente: el log unificado cross-pane no tiene fecha de envío y está declarado como gap abierto.
  • Con salvedad: la detección de hardlinks por file ID falla en algunas configuraciones de Windows, donde cualquier par de archivos parece idéntico.
  • No cubierto: el post no evalúa alternativas de aislamiento por contenedor tipo Docker ni entornos como WSL2 para este problema específico, así que cualquier comparación por ese lado sería especulación.

Errores comunes al correr agentes paralelos en Windows

Estos son los tropiezos que el texto documenta con casos reales, no hipotéticos.

  • Confiar en HOME para aislar sesiones. El resultado es el agente “medio redirigido”: config en un lado, identidad de git en otro. Corrección: USERPROFILE y APPDATA propios por proceso, además del CWD.
  • Liberar el directorio con Set-Location de PowerShell. Como es un concepto de provider, el cwd real del proceso no cambia, el borrado pasa y creés que el bug era imaginario. Corrección: usar lo que setee el cwd real del sistema operativo.
  • Matar solo el proceso padre. El hijo (node.exe en el caso documentado) heredó el directorio y sigue bloqueando. Corrección: matar el árbol completo, buscando por prefijo de ruta normalizada.
  • Correr git worktree prune antes de tiempo. O no tiene nada que podar porque la metadata ya se fue, o podás metadata que referencia el directorio que estás por borrar. Corrección: prune siempre último.
  • Reportar éxito con un borrado parcial. La entrada reaparece viéndose sana y queda imposible de eliminar desde la UI. Corrección: si un paso falla, conservar el registro y mostrar el error.

Preguntas Frecuentes

¿Cómo ejecutar múltiples agentes Claude en paralelo sin conflictos en Windows?

Cada agente necesita su propio USERPROFILE, APPDATA y directorio de trabajo mediante git worktrees, con la configuración compartida enlazada vía junctions o hardlinks. Según el post de NestMux, sin ese aislamiento dos agentes terminan compartiendo la sesión autenticada y la identidad de git.

¿Por qué mi script falla con “Permission denied” al borrar carpetas de procesos paralelos?

Porque Windows no deja borrar un directorio que es el directorio actual de algún proceso vivo, y git reporta esa negativa como error de permisos. La solución es matar el árbol de procesos completo que retiene el handle antes de tocar el filesystem.

¿Cuál es la diferencia entre symlinks, junctions y hardlinks en Windows?

El symlink de archivo requiere elevación o Modo Desarrollador; la junction no requiere privilegios pero solo sirve para directorios; el hardlink es el fallback para archivos y no se detecta con lstat().isSymbolicLink(), así que exige comparar file IDs.

¿Qué hacer después de que falla un agente paralelo para limpiar correctamente?

Primero matá al proceso que retiene el handle, árbol incluido; después borrá el directorio a mano; y recién al final corrés git worktree prune. Si alguno de los pasos falla, conviene conservar el registro y reportar el error en lugar de darlo por eliminado.

¿Es más difícil ejecutar agentes en paralelo en Windows que en Linux?

Sí, en tres puntos concretos: el borrado de directorios en uso falla (en Linux el mismo removal funciona), HOME no estandariza las rutas de configuración, y los symlinks de archivo exigen privilegios. Encima, los mensajes de error apuntan hacia permisos cuando el problema es otro.

Conclusión

Lo que cambió con este post no es una herramienta nueva sino un mapa de fracasos: quién retiene el handle, qué estado deja un borrado fallido y si tu registro del mundo sigue coincidiendo con el disco. En Windows ese conjunto de fallos es distinto al de Linux, y los mensajes de error son peores, así que conviene diseñar el teardown antes que el launch.

Si vas a armar tu propio setup, la lista corta es esta: registrá el cwd de cada panel al spawn, normalizá rutas antes de matar procesos, poné timeout de diez minutos a cada comando de setup, redactá secretos al entrar en los logs y reconciliá tu lista de worktrees contra git en cada lectura. Y si ya tenés un teardown que sobrevive a un run fallido, el autor del post está buscando referencias; la puerta está abierta.

Fuentes

Desplazarse hacia arriba