Flujo de PR con Worktree Aislado
Procedimiento para enviar un cambio como pull request desde un worktree git temporal
cortado de la rama por defecto de origin, cuando el checkout principal no debe
tocarse — porque está sucio (WIP sin commit), detrás del remoto, o
compartido con otros agentes. El flujo cubre la creación del worktree, brechas
de dependencias en worktrees nuevos, espera acotada de CI y protecciones de
limpieza conscientes del modo de fusión.
Por Qué Aislar con Worktree
Cuando múltiples sesiones de agente entran al mismo directorio de trabajo (por
ejemplo, a través de lanzamiento de sesiones por lotes), el HEAD de git, la rama
actual y los cambios sin commit son estado compartido — cada sesión mueve el
piso bajo la otra. En un incidente medido, en menos de un minuto de que una
sesión creara una rama con git switch -c, una segunda sesión en el mismo
checkout se bifurcó de ella, hizo commit y fusionó a la rama por defecto.
Patrón de detección (confirma una colisión en minutos):
- Señales: aparecen archivos que no escribiste, o
git statusya no coincide con la instantánea que tomaste cuando tu sesión comenzó. - Escrutinio de marcas de tiempo:
ls --time-style=full-iso(o el equivalente de tu plataforma). Varios archivos compartiendo el mismo milisegundo significa que una operación git (checkout / merge / stash) los escribió, no ediciones manuales o secuenciales de LLM. git reflog --date=isoaclara la línea de tiempo: quién hizo checkout, commit, o merge, y cuándo.- Si tu entorno de agente proporciona una función de listado de sesiones, úsala para encontrar otras sesiones en ejecución con el mismo directorio de trabajo.
Reglas de cesión una vez que se confirma una colisión:
- El lado que hizo commit primero permanece como escritor. El recién llegado deja de escribir: continuar haciendo commit o cambiando de rama mueve el HEAD de la otra sesión y rompe ambas.
- El recién llegado agrega valor como verificador en su lugar: revisión adversaria del resultado del escritor (afirmaciones del doc vs código, re-ejecutar pruebas, comprobaciones previas a la publicación) y entregables solo en chat que no tocan archivos.
- Limpia solo las referencias intermedias que tú mismo creaste (por ejemplo, una rama vacía); nunca toques referencias que la otra sesión haya usado.
- Los registros de solo añadidura son seguros para que ambas sesiones escriban, bajo encabezados de sección separados.
Prevención: un repositorio = una sesión de escritura. Cuando múltiples agentes deben
trabajar en el mismo repositorio, aísla cada uno en su propio worktree — que es lo
que hace este skill. El hábito de detección más barato es re-comparar git status
contra tu instantánea de inicio de sesión justo después de comenzar a trabajar.
Portabilidad
Este flujo fue endurecido en Windows (PowerShell más Git Bash), pero los comandos
git y gh son multiplataforma. Donde los shells difieren, se dan ejemplos tanto
para PowerShell como para POSIX. CI incluye trabajos de PowerShell 7 para las
protecciones de limpieza sintéticas y el escáner de marcadores privados en Ubuntu 24.04
y macOS 15 nativo además de los hosts Windows. Los tres trabajos pasaron en
PR #7 ejecución 30216166105.
Las piezas específicas de Windows — uniones de directorio y sus semánticas de
eliminación — tienen equivalentes de enlaces simbólicos POSIX anotados en línea.
Cuándo Usarlo
- El checkout principal está sucio (WIP sin commit), detrás del remoto, o compartido con otros agentes, y cortar una rama directamente en él podría romper ese WIP.
- Incluso para una pequeña corrección de higiene: si el checkout existente está detrás por muchos commits y lleva WIP sucio, un worktree aislado es la ruta más segura (probado en campo).
- Se asume un flujo de trabajo de una rama por tarea (nunca hacer commit directamente a la rama por defecto). Este skill cubre solo la mecánica de dónde vive esa rama: un worktree temporal fuera del checkout sucio.
Procedimiento
-
Verifica el estado, de forma no destructiva. Ejecuta cada comando
gitcon-C <repo>y cada comandoghcon--repo <owner>/<name>, para que nada dependa del directorio actual — los entornos de agente pueden restablecer el directorio de trabajo entre llamadas, y sin--repo,ghfalla con "not a git repository" cuando se invoca fuera del repositorio.git -C <repo> status --short # registra el WIP previo al trabajo (verificado sin cambios al final) git -C <repo> branch --show-current git -C <repo> fetch origin gh repo view <owner>/<name> --json defaultBranchRef -q .defaultBranchRef.name # usa el resultado como <default> abajoLos mismos comandos funcionan sin cambios en shells POSIX.
-
Crea un worktree temporal desde la rama por defecto de
origin, sin tocar el checkout sucio. Sustituye<default>del paso 1 — no codifiquesmain; los repositorios cuyo predeterminado esmastersiguen siendo comunes.git -C <repo> worktree add -b fix/<task> ..\_worktrees\<task> origin/<default>POSIX:
git -C <repo> worktree add -b fix/<task> ../_worktrees/<task> origin/<default>Coloca el worktree en algún lugar confirmado como escribible. Los directorios temporales del sistema a veces no son escribibles desde entornos de agente enjaulados (probado en campo). Prefiere un directorio
_worktrees/junto al repositorio o el directorio temporal designado por tu entorno, y antes de ejecutar algo pesado, escribe un archivo de prueba y bórralo para confirmar el acceso de escritura. -
Mantén el diff mínimo. Edita (o copia) solo los archivos que la tarea necesita, dentro del worktree. No arrastres el WIP no rastreado del checkout principal en su totalidad — el upstream u otros agentes pueden agregar archivos con los mismos nombres, y los archivos extra causan conflictos add/add en el momento de la fusión o contaminan el diff del PR. Si debes incluir una copia de un archivo de dependencia sin commit del checkout base, indica en el cuerpo del PR qué lado gana en la fusión (lección de campo: hacer esto explícitamente evitó una mala auto-resolución).
-
Maneja las brechas de dependencias en worktrees nuevos. Un worktree nuevo no tiene
node_modulesni virtualenv (probado en campo).-
Node: ejecuta
npm cien el worktree (requiere un archivo de bloqueo). Para verificación ligera solamente, un enlace temporal alnode_modulesdel checkout principal también funciona — pero elimina el enlace antes de la limpieza (condición de seguridad 5). Unión de PowerShell:New-Item -ItemType Junction -Path <worktree>\node_modules -Target <repo>\node_modules # La eliminación debe borrar solo el enlace, nunca el destino: usa rmdir. cmd /c rmdir "<worktree>\node_modules"Enlace simbólico POSIX:
ln -s <repo>/node_modules <worktree>/node_modules # Elimina el enlace en sí con rm simple (sin -r, sin barra diagonal final). rm <worktree>/node_modules -
Python: reutiliza el intérprete del entorno virtual del repositorio principal y apúntalo a las fuentes del worktree, ejecutando solo las pruebas objetivo. PowerShell:
$env:PYTHONPATH = "<worktree>\src" & <repo>\.venv\Scripts\python.exe -m pytest <worktree>\tests\<target_test>.pyPOSIX:
PYTHONPATH=<worktree>/src <repo>/.venv/bin/python -m pytest <worktree>/tests/<target_test>.py
-
-
Cuando la verificación pase: haz commit, empuja y crea el PR. Escribe el mensaje de commit en un archivo y pásalo con
git commit -F <msgfile>— los mensajes multilínea en línea son frágiles bajo el quoting del shell, especialmente en Windows.git -C <worktree> add <solo archivos objetivo> git -C <worktree> commit -F <msgfile> git -C <worktree> push -u origin fix/<task> gh pr create --repo <owner>/<name> --head fix/<task> ... -
Espera por la CI de protección de rama con sondeo acotado.
gh pr checks --watches una espera ilimitada — los arneses de agente y las capas de aprobación pueden rechazarlo o colgarse (probado en campo). Usa comprobaciones únicas con un límite de intentos:gh pr checks <pr-number> --repo <owner>/<name> --watch=false gh pr view <pr-number> --repo <owner>/<name> --json state,statusCheckRollupBucle acotado, POSIX. Cuando el arnés bloquea
sleepen primer plano (Claude Code lo hace), ejecuta el bucle como tarea en segundo plano, o salta el bucle e intercala comprobaciones únicas entre otras piezas de trabajo:for i in $(seq 1 10); do gh pr checks <pr-number> --repo <owner>/<name> --watch=false && break; sleep 30; doneBucle acotado, PowerShell:
for ($i = 1; $i -le 10; $i++) { gh pr checks <pr-number> --repo <owner>/<name> --watch=false if ($LASTEXITCODE -eq 0) { break } Start-Sleep -Seconds 30 }El límite e intervalo correctos dependen de la duración de CI del repositorio (no verificado en general — ajusta por repositorio).
-
Fusiona — y decide primero el modo de fusión, porque determina la verificación de trabajo no fusionado previa a la limpieza (condición de seguridad 2):
--merge(commit de fusión): los commits de la rama se convierten en ancestros de la rama por defecto, así que verifica conmerge-base --is-ancestory elimina conbranch -d(condición de seguridad 2a; probado en campo).--squash/--rebase: los commits de la rama nunca se convierten en ancestros de la rama por defecto, así que--is-ancestoren la rama sale con 1 para siempre, incluso después de una fusión completamente exitosa. Usa la protección "MERGED + coincidencia headRefOid" en su lugar (condición de seguridad 2b).
gh pr merge <pr-number> --repo <owner>/<name> --merge # repos con política de squash: --squash, luego usa protección 2b gh pr view <pr-number> --repo <owner>/<name> --json state,mergedAt,mergeCommitComportamientos conocidos alrededor de
gh pr merge(probado en campo):- Si la rama local por defecto está verificada en otro worktree,
gh pr merge --delete-branchejecutado desde dentro del repositorio puede fallar durante su post-procesamiento local aunque la fusión remota haya tenido éxito. Ejecutar la fusión con--repodesde fuera del repositorio omite el post-procesamiento local por completo, evitando la clase de fallo de antemano. - Si de todos modos se reporta un fallo, verifica
gh pr view --json stateprimero — el lado remoto a menudo ya estáMERGED. Si es así, completa los pasos locales restantes (eliminación de rama, remoción de worktree) manualmente después de las condiciones de seguridad a continuación. gh pr mergeejecutado desde dentro del repositorio puede hacer fast-forward del worktree actual a la rama por defecto; ver grandes actualizaciones de PRs anteriores en su salida no es una anomalía.
Condiciones de Seguridad
Antes de cualquier paso destructivo (eliminación de worktree, borrado de rama, prune), verifica todo lo siguiente en tiempo de ejecución — ejecuta la verificación ANTES de la eliminación, nunca después. Omitir una verificación previa a la eliminación y "verificar después" anula la protección: un error de trabajo no fusionado se manifestaría solo una vez que la rama ya no existe (lección de campo). En checkouts compartidos, el estado puede variar entre cuándo se aprobó un paso y cuándo se ejecuta; si ha pasado tiempo, vuelve a ejecutar las comprobaciones inmediatamente antes de ejecutar (probado en campo).
-
gh pr view <pr-number> --repo <owner>/<name> --json state,mergedAtreportaMERGED. -
Verificación de trabajo no fusionado acorde al modo de fusión. No mezcles los modos: después de un squash, la protección 2a nunca pasa incluso para un PR fusionado correctamente; y
branch -ddespués de un squash o se cuela por la lógica de fusionado a upstream (siorigin/fix/...aún existe — lo cual no es garantía de ancestro) o es rechazado para siempre como "not fully merged" (si el upstream ya no está).- 2a — Modo
--merge(commit de fusión):git -C <repo> merge-base --is-ancestor fix/<task> origin/<default>sale con 0 (verifica$LASTEXITCODEen PowerShell,echo $?en shells POSIX). Elimina la rama con-d, para que git mismo aún rechace si algo quedó sin fusionar (probado en campo). Advertencia:-djuzga "fusionado" contra el upstream de la rama si está configurado, de lo contrario contra HEAD — así que después de que la rama remota se elimina y se poda, y con el HEAD del checkout sentado en una rama anterior no relacionada,-dpuede rechazar connot fully mergedaunque el PR se haya fusionado correctamente (probado en campo). En ese caso, eliminar con-Des aceptable solo inmediatamente después de que la verificación--is-ancestorhaya salido con 0 — la misma forma de protección-luego--Dque en 2b; un-Dsin la protección sigue prohibido. - 2b — Modo
--squash/--rebase: obténstate,mergeCommityheadRefOida través degh pr view <pr-number> --repo <owner>/<name> --json state,mergeCommit,headRefOid. Nota quemergeCommites un objeto JSON — el hash del commit es su campooid(-q .mergeCommit.oidlo extrae);headRefOides una cadena simple. Luego confirma ambos: (i)git -C <repo> merge-base --is-ancestor <mergeCommit-oid> origin/<default>sale con 0, y (ii)git -C <repo> rev-parse fix/<task>es igual aheadRefOid. La verificación (ii) es el reemplazo del modo squash para "no quedan commits atrás": prueba que el head del PR fusionado es exactamente la punta de tu rama local. Solo cuando ambas se cumplen, elimina con ungit branch -Dexplícito (esta protección sustituye a-d). La topología y las rutas de rechazo de la protección 2b están cubiertas por historiales Git sintéticos desechables enscripts/test-cleanup-guards.ps1. La prueba aísla la configuración Git del sistema/global, hooks, firmado yrebase.updateRefs; captura, limpia y restaura cada variableGIT_*ambiente para que las rutas de repositorio/seguimiento no puedan escapar del fixture; y restringe la limpieza recursiva a su hijo temporal directo generado con comparación de rutas consciente del SO y rechazo de reparse points. La ruta de squash en vivo de GitHub se midió el 2026-07-23 con PR #2:MERGED,mergeCommitaterrizado, puntas locales y remotas sin cambios coincidiendo conheadRefOid,-Dlocal protegido y eliminación remota explícita, todo pasó. La ruta de rebase en vivo de GitHub se midió el 2026-07-26 con PR #5:MERGED, unmergeCommitaterrizado reescrito, el head original fuera de la ascendencia de la rama por defecto, puntas locales y remotas sin cambios coincidiendo conheadRefOid,-Dlocal protegido, eliminación remota explícita y worktree propio limpio...
- 2a — Modo
…
[truncado para traducción]